From reebalazs at codespeak.net Mon Jan 1 14:23:57 2007 From: reebalazs at codespeak.net (reebalazs at codespeak.net) Date: Mon, 1 Jan 2007 14:23:57 +0100 (CET) Subject: [z3-checkins] r36090 - z3/jsonserver/branch/merge/concatresource/compression/thirdparty Message-ID: <20070101132357.2755D1007F@code0.codespeak.net> Author: reebalazs Date: Mon Jan 1 14:23:54 2007 New Revision: 36090 Modified: z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py Log: Update javascript packer Modified: z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py (original) +++ z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py Mon Jan 1 14:23:54 2007 @@ -326,7 +326,7 @@ self.protect(r"""\s+(\/[^\/\n\r\*][^\/\n\r]*\/g?i?)""") self.protect(r"""([^\w\$\/'"*)\?:]\/[^\/\n\r\*][^\/\n\r]*\/g?i?)""") # multiline comments - self.sub(r'/\*.*?\*/', '', re.DOTALL) + self.sub(r'/\*(?!@).*?\*/', '', re.DOTALL) # one line comments self.sub(r'\s*//.*$', '', re.MULTILINE) # strip whitespace at the beginning and end of each line From reebalazs at codespeak.net Mon Jan 1 15:31:10 2007 From: reebalazs at codespeak.net (reebalazs at codespeak.net) Date: Mon, 1 Jan 2007 15:31:10 +0100 (CET) Subject: [z3-checkins] r36093 - z3/jsonserver/branch/merge/concatresource/compression/thirdparty Message-ID: <20070101143110.0B9DD1007F@code0.codespeak.net> Author: reebalazs Date: Mon Jan 1 15:31:05 2007 New Revision: 36093 Modified: z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py Log: Update javascript compression to newest packer.py Modified: z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py (original) +++ z3/jsonserver/branch/merge/concatresource/compression/thirdparty/packer.py Mon Jan 1 15:31:05 2007 @@ -1,7 +1,7 @@ # # packer.py # -# Copyright (c) 2006 Florian Schulze +# Copyright (c) 2006-2007 Florian Schulze # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to @@ -21,7 +21,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -import re +import re, unittest, textwrap class KeywordMapper: @@ -368,44 +368,554 @@ self.protect(r'''("(?:\\"|\\\n|.)*?")''') # strip whitespace self.sub(r'^[ \t\r\f\v]*(.*?)[ \t\r\f\v]*$', r'\1', re.MULTILINE) - # remove comment contents - self.sub(r'/\*.*?( ?[\\/*]*\*/)', r'/*\1', re.DOTALL) - # remove lines with comments only (consisting of stars only) - self.sub(r'^/\*+\*/$', '', re.MULTILINE) + if level == 'full': + # remove comments + self.sub(r'/\*.*? ?[\\/*]*\*/', r'', re.DOTALL) + #remove more whitespace + self.sub(r'\s*([{,;:])\s+', r'\1') + else: + # remove comment contents + self.sub(r'/\*.*?( ?[\\/*]*\*/)', r'/*\1', re.DOTALL) + # remove lines with comments only (consisting of stars only) + self.sub(r'^/\*+\*/$', '', re.MULTILINE) # excessive newlines self.sub(r'\n+', '\n') # first newline self.sub(r'^\n', '') - if level == 'full': - #remove more whitespace - self.sub(r'([{,;])\s+', r'\1') -## jspacker = JavascriptPacker('safe') -## jspacker_full = JavascriptPacker('full') +# be aware that the initial indentation gets removed in the following tests, +# the inner indentation is preserved though (see textwrap.dedent) +js_compression_tests = ( + ( + 'standardJS', + """\ + /* a comment */ + + function dummy() { + + var localvar = 10 // one line comment + + document.write(localvar); + return 'bar' + } + """, + """\ + function dummy(){var localvar=10 + document.write(localvar);return 'bar'} + """, + 'safe' + ), + ( + 'standardJS', + """\ + /* a comment */ + + function dummy() { + + var localvar = 10 // one line comment + + document.write(localvar); + return 'bar' + } + """, + """\ + function dummy(){var localvar=10 + document.write(localvar);return'bar'}""", + 'full' + ), + ( + 'stringProtection', + """ + var leafnode = this.shared.xmldata.selectSingleNode('//*[@selected]'); + var portal_url = 'http://127.0.0.1:9080/plone'; + """, + """var leafnode=this.shared.xmldata.selectSingleNode('//*[@selected]');var portal_url='http://127.0.0.1:9080/plone';""" + ), + ( + 'newlinesInStrings', + r"""var message = "foo: " + foo + "\nbar: " + bar;""", + r"""var message="foo: "+foo+"\nbar: "+bar;""" + ), + ( + 'escapedStrings', + r"""var message = "foo: \"something in quotes\"" + foo + "\nbar: " + bar;""", + r"""var message="foo: \"something in quotes\""+foo+"\nbar: "+bar;""" + ), + ( + 'whitspaceAroundPlus', + """\ + var message = foo + bar; + message = foo++ + bar; + message = foo + ++bar; + """, + """\ + var message=foo+bar;message=foo++ +bar;message=foo+ ++bar;""" + ), + ( + 'whitspaceAroundMinus', + """\ + var message = foo - bar; + message = foo-- - bar; + message = foo - --bar; + """, + """\ + var message=foo-bar;message=foo-- -bar;message=foo- --bar;""" + ), + ( + 'missingSemicolon', + """\ + var x = function() { + + } /* missing ; here */ + next_instr; + """, + """\ + var x=function(){} + next_instr;""", + 'safe' + ), + # be aware that the following produces invalid code. You *have* to add + # a semicolon after a '}' followed by a normal instruction + ( + 'missingSemicolon', + """\ + var x = function() { + + } /* missing ; here */ + next_instr; + """, + """\ + var x=function(){}next_instr;""", + 'full' + ), + # excessive semicolons after curly brackets get removed + ( + 'nestedCurlyBracketsWithSemicolons', + """\ + function dummy(a, b) { + if (a > b) { + do something + } else { + do something else + }; + }; + next_instr; + """, + """\ + function dummy(a,b){if(a>b){do something} else{do something else}};next_instr;""", + 'safe' + ), + ( + 'nestedCurlyBracketsWithSemicolons', + """\ + function dummy(a, b) { + if (a > b) { + do something + } else { + do something else + }; + }; + next_instr; + """, + """\ + function dummy(a,b){if(a>b){do something}else{do something else}};next_instr;""", + 'full' + ), +) + + +css_safe_compression_tests = ( + ( + 'commentCompression', + """ + /* this is a comment */ + #testElement { + property: value; /* another comment */ + } + /**********/ + /* this is a multi + line comment */ + #testElement { + /* yet another comment */ + property: value; + } + """, + """\ + /* */ + #testElement { + property: value; /* */ + } + /* */ + #testElement { + /* */ + property: value; + } + """ + ), + ( + 'newlineCompression', + """ + + + /* this is a comment */ + + #testElement { + property: value; /* another comment */ + } + + /* this is a multi + line comment */ + #testElement { + + /* yet another comment */ + property: value; + + } + + + """, + """\ + /* */ + #testElement { + property: value; /* */ + } + /* */ + #testElement { + /* */ + property: value; + } + """ + ), + # see http://www.dithered.com/css_filters/index.html + ( + 'commentHacks1', + """ + #testElement { + property/**/: value; + property/* */: value; + property /**/: value; + property: /**/value; + } + """, + """\ + #testElement { + property/**/: value; + property/* */: value; + property /**/: value; + property: /**/value; + } + """ + ), + ( + 'commentHacks2', + """ + selector/* */ { } + """, + """\ + selector/* */ { } + """ + ), + ( + 'commentHacks3', + """ + selector/* foobar */ { } + """, + """\ + selector/* */ { } + """ + ), + ( + 'commentHacks4', + """ + selector/**/ { } + """, + """\ + selector/**/ { } + """ + ), + ( + 'commentHacks5', + """ + /* \*/ + rules + /* */ + """, + """\ + /* \*/ + rules + /* */ + """ + ), + ( + 'commentHacks6', + """ + /* foobar \*/ + rules + /* */ + """, + """\ + /* \*/ + rules + /* */ + """ + ), + ( + 'commentHacks7', + """ + /*/*/ + rules + /* */ + """, + """\ + /*/*/ + rules + /* */ + """ + ), + ( + 'commentHacks8', + """ + /*/*//*/ + rules + /* */ + """, + """\ + /*/*//*/ + rules + /* */ + """ + ), + ( + 'stringProtection', + """ + /* test string protection */ + #selector, + #another { + content: 'foo; bar'; + } + """, + """\ + /* */ + #selector, + #another { + content: 'foo; bar'; + } + """ + ), +) + +css_full_compression_tests = ( + ( + 'commentCompression', + """ + /* this is a comment */ + #testElement { + property: value; /* another comment */ + } + /**********/ + /* this is a multi + line comment */ + #testElement { + /* yet another comment */ + property: value; + } + """, + """\ + #testElement{property:value;} + #testElement{property:value;} + """ + ), + ( + 'newlineCompression', + """ + + + /* this is a comment */ + + #testElement { + property: value; /* another comment */ + } + + /* this is a multi + line comment */ + #testElement { + + /* yet another comment */ + property: value; + + } + + + """, + """\ + #testElement{property:value;} + #testElement{property:value;} + """ + ), + # see http://www.dithered.com/css_filters/index.html + # in full compression all hacks get removed + ( + 'commentHacks1', + """ + #testElement { + property/**/: value; + property/* */: value; + property /**/: value; + property: /**/value; + } + """, + """\ + #testElement{property:value;property:value;property:value;property:value;} + """ + ), + ( + 'commentHacks2', + """ + selector/* */ { } + """, + """\ + selector{} + """ + ), + ( + 'commentHacks3', + """ + selector/* foobar */ { } + """, + """\ + selector{} + """ + ), + ( + 'commentHacks4', + """ + selector/**/ { } + """, + """\ + selector{} + """ + ), + ( + 'commentHacks5', + """ + /* \*/ + rules + /* */ + """, + """\ + rules + """ + ), + ( + 'commentHacks6', + """ + /* foobar \*/ + rules + /* */ + """, + """\ + rules + """ + ), + ( + 'commentHacks7', + """ + /*/*/ + rules + /* */ + """, + """\ + rules + """ + ), + ( + 'commentHacks8', + """ + /*/*//*/ + rules + /* */ + """, + """\ + rules + """ + ), + ( + 'stringProtection', + """ + /* test string protection and full compression */ + #selector, + #another { + content: 'foo; bar'; + } + """, + """\ + #selector,#another{content:'foo; bar';} + """ + ), +) + +class PackerTestCase(unittest.TestCase): + def __init__(self, name, input, output, packer): + unittest.TestCase.__init__(self) + self.name = name + self.input = input + self.output = output + self.packer = packer + + def __str__(self): + return self.name + + def runTest(self): + self.assertEqual(self.packer.pack(self.input), self.output) + + +def test_suite(): + suite = unittest.TestSuite() + + jspacker = { + 'safe': JavascriptPacker('safe'), + 'full': JavascriptPacker('full'), + } + csspacker = { + 'safe': CSSPacker('safe'), + 'full': CSSPacker('full'), + } + + for info in js_compression_tests: + name = info[0] + input = textwrap.dedent(info[1]) + output = textwrap.dedent(info[2]) + if (len(info) == 4): + compression = info[3].split(",") + else: + compression = ("safe", "full") + + for packer in compression: + suite.addTest(PackerTestCase("%s (%s)" % (name, packer), + input, output, + jspacker[packer])) + + packer = "safe" + for name, input, output in css_safe_compression_tests: + input = textwrap.dedent(input) + output = textwrap.dedent(output) + + suite.addTest(PackerTestCase("%s (%s)" % (name, packer), + input, output, + csspacker[packer])) + + packer = "full" + for name, input, output in css_full_compression_tests: + input = textwrap.dedent(input) + output = textwrap.dedent(output) + + suite.addTest(PackerTestCase("%s (%s)" % (name, packer), + input, output, + csspacker[packer])) -## def run(): - ## script = open('cssQuery.js').read() - ## script = jspacker_full.pack(script) - ## open('output.js','w').write(script) - ## mapper = JavascriptKeywordMapper() - ## mapper.analyse(script) - ## keywords = mapper.getKeywords() - ## script = mapper.sub(script) - ## f = open('output1.js','w') - ## #f.write("keywords='%s'.split('|');\n" % "|".join(keywords)) - ## #f.write(mapper.getDecodeFunction(name='__dEcOdE')) - ## f.write(mapper.getDecoder(script)) - ## for index, keyword in enumerate(keywords): - ## encoded = mapper._encode(index) - ## if keyword == '': - ## replacement = encoded - ## else: - ## replacement = keyword - ## regexp = re.compile(r'\b%s\b' % encoded) - ## script = regexp.sub(lambda m: replacement, script) - ## open('output2.js','w').write(script) + return suite -## if __name__=='__main__': - ## run() +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') From ltucker at codespeak.net Wed Jan 3 02:58:18 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 3 Jan 2007 02:58:18 +0100 (CET) Subject: [z3-checkins] r36114 - z3/deliverance/trunk/deliverance Message-ID: <20070103015818.29E1810064@code0.codespeak.net> Author: ltucker Date: Wed Jan 3 02:58:16 2007 New Revision: 36114 Modified: z3/deliverance/trunk/deliverance/proxyapp.py Log: add missing quote Modified: z3/deliverance/trunk/deliverance/proxyapp.py ============================================================================== --- z3/deliverance/trunk/deliverance/proxyapp.py (original) +++ z3/deliverance/trunk/deliverance/proxyapp.py Wed Jan 3 02:58:16 2007 @@ -56,7 +56,7 @@ class DebugHeaders(object): translate_keys = {'CONTENT_LENGTH': 'HTTP_CONTENT_LENGTH', - 'CONTENT_TYPE': 'HTTP_CONTENT_TYPE} + 'CONTENT_TYPE': 'HTTP_CONTENT_TYPE'} def __init__(self, app): self.app = app From kobold at codespeak.net Thu Jan 4 10:42:43 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Thu, 4 Jan 2007 10:42:43 +0100 (CET) Subject: [z3-checkins] r36140 - z3/sqlos/branch/kobold-sqlos/src/sqlos Message-ID: <20070104094243.C5B8110070@code0.codespeak.net> Author: kobold Date: Thu Jan 4 10:42:42 2007 New Revision: 36140 Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml Log: Modified configure.zcml to adapt psycopg2 connections. Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml (original) +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml Thu Jan 4 10:42:42 2007 @@ -146,6 +146,15 @@ /> + + + + Author: ltucker Date: Thu Jan 4 19:27:03 2007 New Revision: 36155 Modified: z3/deliverance/trunk/deliverance/faketestingapps.py z3/deliverance/trunk/deliverance/utils.py Log: allow spaces inside CSS urls, update faketestingapps examples Modified: z3/deliverance/trunk/deliverance/faketestingapps.py ============================================================================== --- z3/deliverance/trunk/deliverance/faketestingapps.py (original) +++ z3/deliverance/trunk/deliverance/faketestingapps.py Thu Jan 4 19:27:03 2007 @@ -21,7 +21,7 @@ Usage:: [filter-app:switchedapp] - paste.filter_app_factory = deliverance.faketestingapps.Switcher + paste.filter_app_factory = deliverance.faketestingapps:Switcher /bad_content.html = 10 next = static @@ -61,7 +61,7 @@ Usage:: [filter-app:pauser] - paste.filter_app_factory = deliverance.faketestingapps.Pauser + paste.filter_app_factory = deliverance.faketestingapps:Pauser # 10% of the time: probability = 10 # pause 5 seconds: Modified: z3/deliverance/trunk/deliverance/utils.py ============================================================================== --- z3/deliverance/trunk/deliverance/utils.py (original) +++ z3/deliverance/trunk/deliverance/utils.py Thu Jan 4 19:27:03 2007 @@ -246,7 +246,7 @@ - CSS_URL_PAT = re.compile(r'url\([\"\']*(.*?)[\"\']*\)',re.I) + CSS_URL_PAT = re.compile(r'url\(\s*[\"\']*(.*?)[\"\']*\s*\)',re.I) CSS_IMPORT_PAT = re.compile(r'\@import\s*[\"\'](.*?)[\"\']',re.I) def fixup_css_links(self, elts, base_uri): """ From ltucker at codespeak.net Thu Jan 4 20:12:31 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Thu, 4 Jan 2007 20:12:31 +0100 (CET) Subject: [z3-checkins] r36156 - z3/deliverance/trunk/deliverance Message-ID: <20070104191231.A820D10070@code0.codespeak.net> Author: ltucker Date: Thu Jan 4 20:12:29 2007 New Revision: 36156 Modified: z3/deliverance/trunk/deliverance/interpreter.py Log: remove print Modified: z3/deliverance/trunk/deliverance/interpreter.py ============================================================================== --- z3/deliverance/trunk/deliverance/interpreter.py (original) +++ z3/deliverance/trunk/deliverance/interpreter.py Thu Jan 4 20:12:29 2007 @@ -428,6 +428,13 @@ theme_el.extend(content_els) + def get_content_els(rule, content): + xpath = self.get_content_xpath(rule) + if xpath: + return copy.deepcopy(content.xpath(self.get_content_xpath(rule))) + else: + return copy.deepcopy(rule.xpath("./*")) + def drop_els(self,doc,els): """ From ltucker at codespeak.net Thu Jan 4 20:14:47 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Thu, 4 Jan 2007 20:14:47 +0100 (CET) Subject: [z3-checkins] r36157 - z3/deliverance/trunk/deliverance/test-data/aggregate Message-ID: <20070104191447.8C9DB10070@code0.codespeak.net> Author: ltucker Date: Thu Jan 4 20:14:45 2007 New Revision: 36157 Added: z3/deliverance/trunk/deliverance/test-data/aggregate/ z3/deliverance/trunk/deliverance/test-data/aggregate/example.html z3/deliverance/trunk/deliverance/test-data/aggregate/expected.html z3/deliverance/trunk/deliverance/test-data/aggregate/nav.html z3/deliverance/trunk/deliverance/test-data/aggregate/rules.xml z3/deliverance/trunk/deliverance/test-data/aggregate/theme.html Log: add missing test data for aggregate test Added: z3/deliverance/trunk/deliverance/test-data/aggregate/example.html ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/aggregate/example.html Thu Jan 4 20:14:45 2007 @@ -0,0 +1,10 @@ + + + I am a title + + + Early text

Paragraph one

+

Paragraph two

+ extra + + Added: z3/deliverance/trunk/deliverance/test-data/aggregate/expected.html ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/aggregate/expected.html Thu Jan 4 20:14:45 2007 @@ -0,0 +1,19 @@ + + + + I am a title + + + + + Some text +
+

Paragraph one

+

Paragraph two

+
+ external body text +

+
+
+ + Added: z3/deliverance/trunk/deliverance/test-data/aggregate/nav.html ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/aggregate/nav.html Thu Jan 4 20:14:45 2007 @@ -0,0 +1,11 @@ + + + + +
+ + external body text +

+
+ + Added: z3/deliverance/trunk/deliverance/test-data/aggregate/rules.xml ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/aggregate/rules.xml Thu Jan 4 20:14:45 2007 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + Added: z3/deliverance/trunk/deliverance/test-data/aggregate/theme.html ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/aggregate/theme.html Thu Jan 4 20:14:45 2007 @@ -0,0 +1,10 @@ + + + Example + + + + Some text +
replace this
+ + From kobold at codespeak.net Sun Jan 7 23:16:29 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sun, 7 Jan 2007 23:16:29 +0100 (CET) Subject: [z3-checkins] r36218 - in z3/sqlos/branch/kobold-sqlos/src/sqlos: . ftests Message-ID: <20070107221629.D607610070@code0.codespeak.net> Author: kobold Date: Sun Jan 7 23:16:28 2007 New Revision: 36218 Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml z3/sqlos/branch/kobold-sqlos/src/sqlos/ftests/localutilities.txt Log: Minor changes. Modified: z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml (original) +++ z3/sqlos/branch/kobold-sqlos/src/sqlos/configure.zcml Sun Jan 7 23:16:28 2007 @@ -156,7 +156,6 @@
- >> from zope.app import zapi + >>> import zope.component + >>> import zope.traversing.api >>> from zope.app.folder import Folder >>> from zope.app.component import interfaces as componentInterfaces >>> from zope.app.component.site import LocalSiteManager @@ -26,26 +27,19 @@ Let's get the connection name: >>> from sqlos.interfaces import IConnectionName - >>> connection_name = zapi.getUtility(IConnectionName).name + >>> connection_name = zope.component.getUtility(IConnectionName).name Now we set up a local database utility >>> from zope.rdb.interfaces import IZopeDatabaseAdapter - >>> from zope.app.component.interfaces.registration import ActiveStatus >>> from sqlos.testing.testdb import SQLiteda - >>> from zope.app.utility import UtilityRegistration >>> dbAdapter = SQLiteda(u'dbi://:memory:') - >>> reg = UtilityRegistration(connection_name, - ... IZopeDatabaseAdapter, - ... dbAdapter) - >>> default = sm['default'] - >>> key = default.registrationManager.addRegistration(reg) - >>> zapi.traverse(default.registrationManager, key).status = ActiveStatus + >>> sm.registerUtility(dbAdapter, provided=IZopeDatabaseAdapter, name=connection_name) >>> localUtility = sm.queryUtility(IZopeDatabaseAdapter, connection_name) - >>> localUtility - + >>> localUtility is dbAdapter + True >>> localUtility is dbAdapter True @@ -54,7 +48,7 @@ make sure that our localUtility is not identical to the global sqlite utility that has been registered through ftesting.zcml. - >>> gsm = zapi.getGlobalSiteManager() + >>> gsm = zope.component.getGlobalSiteManager() >>> globalUtility = gsm.queryUtility(IZopeDatabaseAdapter, connection_name) >>> globalUtility is not localUtility True @@ -67,7 +61,7 @@ ... '''create table dog ( ... id integer primary key, ... fullname varchar(50) not null, - ... owner_id integer not null)''') + ... owner varchar(20) not null)''') >>> c = cursor.execute( ... '''create table sample_isolated_person ( ... id integer primary key, From kobold at codespeak.net Sun Jan 7 23:16:42 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sun, 7 Jan 2007 23:16:42 +0100 (CET) Subject: [z3-checkins] r36219 - z3/sqlos/trunk/src/sqlos Message-ID: <20070107221642.619BE10078@code0.codespeak.net> Author: kobold Date: Sun Jan 7 23:16:41 2007 New Revision: 36219 Modified: z3/sqlos/trunk/src/sqlos/adapter.py z3/sqlos/trunk/src/sqlos/configure.zcml Log: Added support for psycopg2da. Modified: z3/sqlos/trunk/src/sqlos/adapter.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/adapter.py (original) +++ z3/sqlos/trunk/src/sqlos/adapter.py Sun Jan 7 23:16:41 2007 @@ -148,6 +148,29 @@ self.supportTransactions = True +class Psycopg2Adapter(ConnectionAdapter, _postgres.builder()): + + def __init__(self, connection): + #The import is needed, as sqlobject uses self.module.Binary uppon + #startup. But until now we dont define .module. There is definitely + #need for a better solution. Propably the other adapters need this as + #well, but i cant test them, as i dont have them. + #Andres Freund - 2005-10-17 + if getattr(self, 'module', None) == None: + import psycopg2 + self.module = psycopg2 + #This is needed, because psycopg provides a optimized + #Binary() function which sqlobject dont get along with. This is + #normally done in sqlobject.postgres.pgconnection __init__ but we + #dont call that. + from sqlobject.postgres import pgconnection + registerConverter(type(psycopg2.Binary('')), + pgconnection.PsycoBinaryConverter) + + super(Psycopg2Adapter, self).__init__(connection) + self.supportTransactions = True + + class SQLiteAdapter(ConnectionAdapter, _sqlite.builder()): def __init__(self, connection): Modified: z3/sqlos/trunk/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/configure.zcml (original) +++ z3/sqlos/trunk/src/sqlos/configure.zcml Sun Jan 7 23:16:41 2007 @@ -105,6 +105,15 @@ /> + + + + Author: kobold Date: Sun Jan 7 23:23:12 2007 New Revision: 36220 Added: z3/sqlos/trunk/src/sqlos/sqlos-configure.zcml z3/sqlos/trunk/src/sqlos/sqlos-ftesting.zcml z3/sqlos/trunk/src/sqlos/sqlos-meta.zcml Log: Added sqlos-*.zcml files for quick installation. Added: z3/sqlos/trunk/src/sqlos/sqlos-configure.zcml ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/sqlos-configure.zcml Sun Jan 7 23:23:12 2007 @@ -0,0 +1 @@ + Added: z3/sqlos/trunk/src/sqlos/sqlos-ftesting.zcml ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/sqlos-ftesting.zcml Sun Jan 7 23:23:12 2007 @@ -0,0 +1 @@ + Added: z3/sqlos/trunk/src/sqlos/sqlos-meta.zcml ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/sqlos-meta.zcml Sun Jan 7 23:23:12 2007 @@ -0,0 +1 @@ + From kobold at codespeak.net Mon Jan 8 16:57:52 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 16:57:52 +0100 (CET) Subject: [z3-checkins] r36286 - in z3/sqlos/trunk/src/sqlos: . interfaces Message-ID: <20070108155752.BF8B91007A@code0.codespeak.net> Author: kobold Date: Mon Jan 8 16:57:48 2007 New Revision: 36286 Modified: z3/sqlos/trunk/src/sqlos/container.py z3/sqlos/trunk/src/sqlos/interfaces/__init__.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Backported minor fixes from the kobold-sqlos branch. Modified: z3/sqlos/trunk/src/sqlos/container.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/container.py (original) +++ z3/sqlos/trunk/src/sqlos/container.py Mon Jan 8 16:57:48 2007 @@ -52,7 +52,7 @@ obj.__parent__ = parent if oldname != name and name is not None: - obj.__name__ = name + obj.__name__ = unicode(name) return obj @@ -100,7 +100,8 @@ """ Return a sequence-like object containing the names associated with the objects that appear in the folder """ - for name, obj in self.items(): yield name + for name, obj in self.items(): + yield name def __iter__(self): return iter(self.keys()) @@ -109,7 +110,8 @@ """ Return a sequence-like object containing the objects that appear in the folder. """ - for name, obj in self.items(): yield obj + for name, obj in self.items(): + yield contained(obj, parent=self, name=name) def items(self): """Return a sequence-like object containing tuples of the form @@ -153,7 +155,7 @@ if factoryName != utility_name: continue try: - obj = utility.get(id) + obj = utility.get(utility.sqlmeta.idType(id)) return contained(obj, parent=self, name=name) except (SQLObjectNotFound, ValueError): # SQlObject raises ValueError if the key is not correct @@ -167,7 +169,7 @@ KeyError is raised. """ try: - return self[name] + return contained(self[name], parent=self, name=name) except KeyError: return default @@ -252,5 +254,5 @@ obj = super(SQLIsolatedContainer, self).__getitem__(name) if hasattr(obj, 'domains'): if self.container_id in obj.domains: - return obj + return contained(obj, parent=self, name=name) raise KeyError, name Modified: z3/sqlos/trunk/src/sqlos/interfaces/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/__init__.py Mon Jan 8 16:57:48 2007 @@ -16,6 +16,7 @@ from zope.schema.vocabulary import SimpleVocabulary from zope.schema import Choice, List from zope.annotation.interfaces import IAttributeAnnotatable +from zope.app.container.interfaces import IContained from sqlobject import NoDefault from sqlobject.dbconnection import DBConnection, DBAPI from sqlobject import _mysql, _postgres, _sybase @@ -267,7 +268,7 @@ similar to select() """ -class ISQLObject(Interface): +class ISQLObject(IContained): # XXX - _idName moved to sqlmeta #_idName = Attribute('Primary Key') Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Mon Jan 8 16:57:48 2007 @@ -13,6 +13,7 @@ from zope.interface import implements from sqlobject.main import SQLObject from sqlobject import StringCol +from zope.app.container.contained import Contained from sqlos.connection import ConnectionDescriptor from sqlos.interfaces import ISQLObject @@ -26,7 +27,7 @@ _transaction.dirty_object_registry.syncUpdateAll() -class SQLOS(SQLObject): +class SQLOS(SQLObject, Contained): """Subclass SQLObject to enable ``lazy updates`` by default, as well as adding knowledge to register ``dirty`` objects with SQLObjectTransactionManager so they get sync'd on transaction @@ -69,8 +70,11 @@ # objects in the cache that have a __parent__ set. # This may be confusing when expect to get a object # which has no __parent__ and thats not what you get. - val = super(SQLOS, self).get(id, connection=connection, - selectResults=selectResults) + try: + val = super(SQLOS, self).get(id, connection=connection, + selectResults=selectResults) + except ValueError: + raise AttributeError, id if getattr(val, '__parent__', None) is not None: val.__parent__ = None val.__name__ = None From kobold at codespeak.net Mon Jan 8 17:13:33 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 17:13:33 +0100 (CET) Subject: [z3-checkins] r36291 - in z3/sqlos/trunk: . includes Message-ID: <20070108161333.3A7D510070@code0.codespeak.net> Author: kobold Date: Mon Jan 8 17:13:31 2007 New Revision: 36291 Added: z3/sqlos/trunk/Makefile - copied unchanged from r36217, z3/sqlos/trunk/makefile Removed: z3/sqlos/trunk/TODO.txt z3/sqlos/trunk/includes/ z3/sqlos/trunk/makefile Modified: z3/sqlos/trunk/CONTRIBUTORS.txt z3/sqlos/trunk/COPYING.txt z3/sqlos/trunk/setup.py Log: Reorganized documentation; moved include files to src/sqlos. Modified: z3/sqlos/trunk/CONTRIBUTORS.txt ============================================================================== --- z3/sqlos/trunk/CONTRIBUTORS.txt (original) +++ z3/sqlos/trunk/CONTRIBUTORS.txt Mon Jan 8 17:13:31 2007 @@ -6,4 +6,5 @@ - Andrew Bennetts (spiv @ #launchpad) - Brian Sutherland (jinty at web.de) - Andres Freund (andres at anarazel.de) - - Adam Groszer + - Adam Groszer (adamg at fw.hu) + - Fabio Tranchitella (fabio@?ranchitella.it) Modified: z3/sqlos/trunk/COPYING.txt ============================================================================== --- z3/sqlos/trunk/COPYING.txt (original) +++ z3/sqlos/trunk/COPYING.txt Mon Jan 8 17:13:31 2007 @@ -3,3 +3,4 @@ Copyright (C) 2003-2004 sqlos, Enfold Systems LLC. Copyright (C) 2005-2006 sqlos, Brian Sutherland +Copyright (C) 2006-2007 sqlos, Fabio Tranchitella Deleted: /z3/sqlos/trunk/TODO.txt ============================================================================== --- /z3/sqlos/trunk/TODO.txt Mon Jan 8 17:13:31 2007 +++ (empty file) @@ -1,14 +0,0 @@ -TODO -==== - -Features --------- - -* Make it un-necessary to register the db adapter class in zcml. This is - required right now as there is no 'correct' way to tell which database a - IZopeConnection is for. - - - fixed for MySQL, should be fixed at least for postgres - -Bugs ----- Deleted: /z3/sqlos/trunk/makefile ============================================================================== --- /z3/sqlos/trunk/makefile Mon Jan 8 17:13:31 2007 +++ (empty file) @@ -1,67 +0,0 @@ -HERE=`pwd` -CSV=${HERE}/csv -ZP=${HERE}/../ -ZH=${HERE}/../../ -PYTHON=python2.4 -z3includes=Zope3/zopeskel/etc/package-includes -Z3BRANCH=trunk - -all : test clean - -doc : - export PYTHONPATH=${ZP} && epydoc -o docs/api --css blue --private-css green -v -n sqlos . - -sorted : - cat coverage_report | sort -k2 -r -n | grep 'sqlos' > coverage_sorted - -coverage : - cd ${ZH} && $(PYTHON) test.py -vpfT --all sqlos > ${HERE}/coverage_report - -coverage_sorted : coverage sorted - -.PHONY: clean -clean: - find . \( -name '*~' -o -name '*.py[co]' -o -name '*.bak' -o -name '#*#' -o -name '\.#*' \) -exec rm {} \; -print - $(PYTHON) setup.py clean - -.PHONY: realclean -realclean: clean - rm -rf dist - rm -rf build - rm -rf Zope3 - -.PHONY: z3-checkout -z3-checkout: - -test -d Zope3 || svn co svn://svn.zope.org/repos/main/Zope3/$(Z3BRANCH) Zope3 - -.PHONY: z3-update -z3-update: z3-checkout - svn up Zope3 - -Zope3: - $(MAKE) z3-checkout - -$(z3includes)/%.zcml: includes/%.zcml Zope3 - cp $< $@ - -.PHONY: sqlos-meta -sqlos-meta: $(z3includes)/sqlos-meta.zcml $(z3includes)/sqlos-configure.zcml $(z3includes)/sqlos-ftesting.zcml - -.PHONY: Zope3-build -Zope3-build: Zope3 - cd Zope3 && $(MAKE) PYTHON=$(PYTHON) inplace - -.PHONY: develop -develop: Zope3-build sqlos-meta - PYTHONPATH=Zope3/src $(PYTHON) setup.py develop --install-dir Zope3/src - -.PHONY: testall -test: develop - cd Zope3 && PYTHONPATH=src $(PYTHON) test.py --test-path=../src -s sqlos - -Zope3/principals.zcml: Zope3 Zope3/sample_principals.zcml - cp Zope3/sample_principals.zcml $@ - -.PHONY: run-sampleapp -run-sampleapp: develop Zope3/principals.zcml $(z3includes)/sqlos.ftesting-configure.zcml - cd Zope3; PYTHONPATH=src ./z3.py Modified: z3/sqlos/trunk/setup.py ============================================================================== --- z3/sqlos/trunk/setup.py (original) +++ z3/sqlos/trunk/setup.py Mon Jan 8 17:13:31 2007 @@ -20,5 +20,5 @@ include_package_data=True, install_requires = [ 'SQLObject>=0.7', - ] # XXX - what else? at least zope, let the users find out;) + ] ) From kobold at codespeak.net Mon Jan 8 17:14:58 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 17:14:58 +0100 (CET) Subject: [z3-checkins] r36292 - in z3/sqlos/trunk: . doc Message-ID: <20070108161458.33CC810078@code0.codespeak.net> Author: kobold Date: Mon Jan 8 17:14:57 2007 New Revision: 36292 Added: z3/sqlos/trunk/doc/CONTRIBUTORS.txt - copied unchanged from r36291, z3/sqlos/trunk/CONTRIBUTORS.txt z3/sqlos/trunk/doc/COPYING.txt - copied unchanged from r36291, z3/sqlos/trunk/COPYING.txt Removed: z3/sqlos/trunk/CONTRIBUTORS.txt z3/sqlos/trunk/COPYING.txt Log: Moved documentation files to the doc directory. Deleted: /z3/sqlos/trunk/CONTRIBUTORS.txt ============================================================================== --- /z3/sqlos/trunk/CONTRIBUTORS.txt Mon Jan 8 17:14:57 2007 +++ (empty file) @@ -1,10 +0,0 @@ -Contributors: - - - Sidnei da Silva (sidnei at awkly.org) - - Alan Runyan (runyaga at runyaga.com) - - Josh LaPlace (josh at clearnoodle.com) - - Andrew Bennetts (spiv @ #launchpad) - - Brian Sutherland (jinty at web.de) - - Andres Freund (andres at anarazel.de) - - Adam Groszer (adamg at fw.hu) - - Fabio Tranchitella (fabio@?ranchitella.it) Deleted: /z3/sqlos/trunk/COPYING.txt ============================================================================== --- /z3/sqlos/trunk/COPYING.txt Mon Jan 8 17:14:57 2007 +++ (empty file) @@ -1,6 +0,0 @@ -sqlos is distributed under the provisions of the Zope Public License -(ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text. - -Copyright (C) 2003-2004 sqlos, Enfold Systems LLC. -Copyright (C) 2005-2006 sqlos, Brian Sutherland -Copyright (C) 2006-2007 sqlos, Fabio Tranchitella From kobold at codespeak.net Mon Jan 8 17:18:35 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 17:18:35 +0100 (CET) Subject: [z3-checkins] r36293 - z3/sqlos/trunk/doc Message-ID: <20070108161835.48DC310078@code0.codespeak.net> Author: kobold Date: Mon Jan 8 17:18:34 2007 New Revision: 36293 Modified: z3/sqlos/trunk/doc/CONTRIBUTORS.txt Log: Fixed my e-mail address. Modified: z3/sqlos/trunk/doc/CONTRIBUTORS.txt ============================================================================== --- z3/sqlos/trunk/doc/CONTRIBUTORS.txt (original) +++ z3/sqlos/trunk/doc/CONTRIBUTORS.txt Mon Jan 8 17:18:34 2007 @@ -7,4 +7,4 @@ - Brian Sutherland (jinty at web.de) - Andres Freund (andres at anarazel.de) - Adam Groszer (adamg at fw.hu) - - Fabio Tranchitella (fabio@?ranchitella.it) + - Fabio Tranchitella (fabio at tranchitella.it) From kobold at codespeak.net Mon Jan 8 17:58:43 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 17:58:43 +0100 (CET) Subject: [z3-checkins] r36298 - in z3/sqlos/trunk/src/sqlos: . tests Message-ID: <20070108165843.7C51910079@code0.codespeak.net> Author: kobold Date: Mon Jan 8 17:58:39 2007 New Revision: 36298 Modified: z3/sqlos/trunk/src/sqlos/__init__.py z3/sqlos/trunk/src/sqlos/configure.zcml z3/sqlos/trunk/src/sqlos/ftesting.zcml z3/sqlos/trunk/src/sqlos/meta.zcml z3/sqlos/trunk/src/sqlos/sampleapp.zcml z3/sqlos/trunk/src/sqlos/tests/__init__.py z3/sqlos/trunk/src/sqlos/tests/test_doctests.py z3/sqlos/trunk/src/sqlos/tests/test_transaction.py z3/sqlos/trunk/src/sqlos/tests/test_verify.py Log: Minor/cosmetic changes and deprecation warnings. Modified: z3/sqlos/trunk/src/sqlos/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/__init__.py Mon Jan 8 17:58:39 2007 @@ -10,24 +10,7 @@ $Id$ """ -from datetime import datetime, date -from sqlobject.sqlbuilder import registerConverter +from zope.deprecation import deprecated -# sqlos.SQLOS is softly deprecated, will go away sometime -# (after at least one major release with deprecation warnings) -# import directly from sqlos.zsqlobject from sqlos.zsqlobject import SQLOS - - -## XXX: What are these?? I am sure there are no tests for them. - jinty -def DateTimeConverter(value, db=None): - return repr(value.isoformat()) - -registerConverter(datetime, DateTimeConverter) - -def DateConverter(value, db=None): - if isinstance(value, datetime): - return repr(value.date().isoformat()) - return repr(value.isoformat()) - -registerConverter(date, DateConverter) +deprecated('SQLOS', 'sqlos.SQLOS is deprecated and will go away in next release') Modified: z3/sqlos/trunk/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/configure.zcml (original) +++ z3/sqlos/trunk/src/sqlos/configure.zcml Mon Jan 8 17:58:39 2007 @@ -1,3 +1,4 @@ + Modified: z3/sqlos/trunk/src/sqlos/sampleapp.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/sampleapp.zcml (original) +++ z3/sqlos/trunk/src/sqlos/sampleapp.zcml Mon Jan 8 17:58:39 2007 @@ -1,3 +1,4 @@ + Modified: z3/sqlos/trunk/src/sqlos/tests/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/__init__.py Mon Jan 8 17:58:39 2007 @@ -1 +0,0 @@ -# import this Modified: z3/sqlos/trunk/src/sqlos/tests/test_doctests.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/test_doctests.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/test_doctests.py Mon Jan 8 17:58:39 2007 @@ -28,3 +28,6 @@ DocTestSuite('sqlos._transaction'), DocTestSuite('sqlos.zsqlobject') ]) + +if __name__=='__main__': + unittest.TextTestRunner().run(test_suite()) Modified: z3/sqlos/trunk/src/sqlos/tests/test_transaction.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/test_transaction.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/test_transaction.py Mon Jan 8 17:58:39 2007 @@ -18,6 +18,7 @@ from zope.testing.doctestunit import DocTestSuite from transaction import get, begin + def doctest_KeepValuesOverExpireSync(): """Regression test for a SQLObject bug in expire. @@ -62,9 +63,10 @@ >>> tearDown() """ - def test_suite(): return unittest.TestSuite([ DocTestSuite(), ]) +if __name__=='__main__': + unittest.TextTestRunner().run(test_suite()) Modified: z3/sqlos/trunk/src/sqlos/tests/test_verify.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/test_verify.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/test_verify.py Mon Jan 8 17:58:39 2007 @@ -16,17 +16,15 @@ $Id$ """ -from zope.interface import Interface, implements, classImplements +import unittest + from zope.interface.verify import verifyClass, verifyObject -from zope.interface.exceptions import DoesNotImplement, BrokenImplementation -from zope.interface.exceptions import BrokenMethodImplementation + from sqlobject.dbconnection import DBConnection, DBAPI from sqlobject import _mysql, _postgres, _sybase from sqlobject.main import SQLObject -from sqlos.interfaces import IDBConnection, \ - IDBAPI, ISQLConnection, ISQLObject +from sqlos.interfaces import IDBConnection, IDBAPI, ISQLConnection, ISQLObject -import unittest class Test(unittest.TestCase): @@ -34,7 +32,7 @@ self.failUnless(verifyClass(ISQLObject, SQLObject)) def testDBConnection(self): - self.failUnless(verifyClass(IDBConnection, DBConnection )) + self.failUnless(verifyClass(IDBConnection, DBConnection)) def testDBAPI(self): self.failUnless(verifyClass(IDBAPI, DBAPI)) From kobold at codespeak.net Mon Jan 8 18:08:01 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 18:08:01 +0100 (CET) Subject: [z3-checkins] r36300 - in z3/sqlos/trunk/src/sqlos: . container Message-ID: <20070108170801.D6B9810079@code0.codespeak.net> Author: kobold Date: Mon Jan 8 18:07:59 2007 New Revision: 36300 Added: z3/sqlos/trunk/src/sqlos/container/ z3/sqlos/trunk/src/sqlos/container/__init__.py - copied unchanged from r36286, z3/sqlos/trunk/src/sqlos/container.py Removed: z3/sqlos/trunk/src/sqlos/container.py Log: Transformed container.py module into a package. Deleted: /z3/sqlos/trunk/src/sqlos/container.py ============================================================================== --- /z3/sqlos/trunk/src/sqlos/container.py Mon Jan 8 18:07:59 2007 +++ (empty file) @@ -1,258 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 Enfold Systems LLC. All rights reserved. -# Copyright (c) 2005-2006 Brian Sutherland. All rights reserved. -# -# This software is distributed under the terms of the Zope Public -# License (ZPL) v2.1. See COPYING.txt for more information. -# -############################################################################## -""" -$Id$ -""" - -import random - -from sqlobject import * -from persistent import Persistent -from zope.interface import implements -from zope.component import IFactory -import zope.component -from zope.app.container.interfaces import IContained -from zope.app.container.contained import ContainedProxy -from zope.app.container.contained import Contained -from zope.app.container.contained import NameChooser -from zope.app.container.constraints import checkFactory -from zope.location.interfaces import ILocation -from zope.proxy import sameProxiedObjects -from zope.exceptions.interfaces import UserError - -from sqlos.interfaces import ISQLObject, ISQLObjectIsolated, IISQLObject -from sqlos.interfaces.container import ISQLObjectContainer -from sqlos.interfaces.container import IIsolatedSQLContainer - -def contained(obj, parent=None, name=None): - """An implementation of zope.app.container.contained.contained - that doesn't generate events, for internal use. - """ - if (parent is None): - raise TypeError, 'Must provide a parent' - - if not IContained.providedBy(obj): - if ILocation.providedBy(obj): - directlyProvides(obj, IContained, directlyProvidedBy(obj)) - else: - obj = ContainedProxy(obj) - - oldparent = obj.__parent__ - oldname = obj.__name__ - - if (oldparent is None) or not (oldparent is parent - or sameProxiedObjects(oldparent, parent)): - obj.__parent__ = parent - - if oldname != name and name is not None: - obj.__name__ = unicode(name) - - return obj - - -class SQLObjectNameChooser(NameChooser): - # XXX: This needs unit tests... - - def chooseName(self, name, obj): - if ISQLObject.providedBy(obj): - # Look for the SQLObject class our object is from, so get all - # allowed factories - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self.context, None, factory): - # get the sqlobject class - utility = zope.component.queryUtility(IISQLObject, name) - if utility is None: - continue - if obj.sqlmeta.table == utility.sqlmeta.table: - # if the tables names are the same, we assume that the - # sqlobject is an instance of that class, perhaps that - # is wrong - return "%s.%s" % (name, obj.id) - raise UserError("Cannot find a name") # XXX better message, i18n? - - -class SQLObjectContainer(Persistent, Contained): - - implements(ISQLObjectContainer) - - def __init__(self): - pass - - def _getAllowedIISQLObjectUtilities(self): - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self, None, factory): - utility = zope.component.queryUtility(IISQLObject, name) - # Someone might have registered a factory implementing - # IISQLObject using for whatever reason. - # in this case queryUtility returns None and we can just - # ignore it - if utility is not None: - yield name, utility - - def keys(self): - """ Return a sequence-like object containing the names - associated with the objects that appear in the folder - """ - for name, obj in self.items(): - yield name - - def __iter__(self): - return iter(self.keys()) - - def values(self): - """ Return a sequence-like object containing the objects that - appear in the folder. - """ - for name, obj in self.items(): - yield contained(obj, parent=self, name=name) - - def items(self): - """Return a sequence-like object containing tuples of the form - (name, object) for the objects that appear in the folder. - """ - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - for obj in utility.select(): - name = '%s.%s' % (utility_name, obj.id) - yield (name, contained(obj, parent=self, name=name)) - - def __getitem__(self, name): - """Return the named object. - - If the object is not found a KeyError is raised. - - lets get a container: - - >>> c = SQLObjectContainer() - - And make sure it doesn't bork on non-string values: - - >>> c[None] - Traceback (most recent call last): - ... - KeyError: ... - >>> c[object()] - Traceback (most recent call last): - ... - KeyError: ... - """ - if not isinstance(name, basestring): - raise KeyError, "%s is not a string" % name - try: - parts = name.split('.') - id = parts[-1] - factoryName = '.'.join(parts[:-1]) - except ValueError: - raise KeyError, name - - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - if factoryName != utility_name: - continue - try: - obj = utility.get(utility.sqlmeta.idType(id)) - return contained(obj, parent=self, name=name) - except (SQLObjectNotFound, ValueError): - # SQlObject raises ValueError if the key is not correct - raise KeyError, name - raise KeyError, name - - def get(self, name, default=None): - """Return the named object, or the value of the default - argument if given and the named object is not found. - If no default is given and the object is not found a - KeyError is raised. - """ - try: - return contained(self[name], parent=self, name=name) - except KeyError: - return default - - def __contains__(self, name): - """Return true if the named object appears in the folder.""" - return self.get(name, None) is not None - - def __len__(self): - """Return the number of objects in the folder.""" - i = 0 - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - i += utility.select().count() # optimal, does not get all objects - return i - - def __delitem__(self, name): - """Delete the named object from the container. - - Raises a KeyError if the object is not found. - """ - obj = self[name] - obj.destroySelf() - - def __setitem__(self, name, content): - return name - - -class SQLIsolatedContainer(SQLObjectContainer): - - implements(IIsolatedSQLContainer) - - _container_id = None - - def _getAllowedIISQLObjectUtilities(self): - # Ignore all utilities not implementing ISQLObjectIsolated - for name, utility in SQLObjectContainer._getAllowedIISQLObjectUtilities(self): - if ISQLObjectIsolated.implementedBy(utility): - yield name, utility - - def _getContainerId(self): - if self._container_id is None: - self._container_id = str(random.random()) # TODO better generation? - return self._container_id - def _setContainerId(self, value): - self._container_id = value - container_id = property(_getContainerId, _setContainerId) - - def __len__(self): - """Return the number of objects in the folder.""" - i = 0 - for name, utility in self._getAllowedIISQLObjectUtilities(): - i += utility.countByDomain(self.container_id) - return i - - def __delitem__(self, name): - """Delete the named object from the container. - - Raises a KeyError if the object is not found. - """ - obj = self[name] - domains = [d for d in obj.domains if d != self.container_id] - if not domains: - obj.destroySelf() - return - obj.domains = tuple(domains) - - def __setitem__(self, name, content): - domains = content.domains - if self.container_id not in domains: - content.domains = domains + (self.container_id, ) - return name - - def items(self): - """Return a sequence-like object containing tuples of the form - (name, object) for the objects that appear in the folder. - """ - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - for obj in utility.selectByDomain(self.container_id): - name = '%s.%s' % (utility_name, obj.id) - yield (name, contained(obj, parent=self, name=name)) - - def __getitem__(self, name): - obj = super(SQLIsolatedContainer, self).__getitem__(name) - if hasattr(obj, 'domains'): - if self.container_id in obj.domains: - return contained(obj, parent=self, name=name) - raise KeyError, name From kobold at codespeak.net Mon Jan 8 19:15:04 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 19:15:04 +0100 (CET) Subject: [z3-checkins] r36307 - in z3/sqlos/trunk/src/sqlos: container tests Message-ID: <20070108181504.197A410076@code0.codespeak.net> Author: kobold Date: Mon Jan 8 19:15:03 2007 New Revision: 36307 Added: z3/sqlos/trunk/src/sqlos/container/isolated.py - copied, changed from r36300, z3/sqlos/trunk/src/sqlos/container/__init__.py z3/sqlos/trunk/src/sqlos/container/standard.py Modified: z3/sqlos/trunk/src/sqlos/container/__init__.py z3/sqlos/trunk/src/sqlos/tests/test_doctests.py Log: Splitted containers in different files (actually, standard and isolated). Modified: z3/sqlos/trunk/src/sqlos/container/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/container/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/container/__init__.py Mon Jan 8 19:15:03 2007 @@ -11,248 +11,5 @@ $Id$ """ -import random - -from sqlobject import * -from persistent import Persistent -from zope.interface import implements -from zope.component import IFactory -import zope.component -from zope.app.container.interfaces import IContained -from zope.app.container.contained import ContainedProxy -from zope.app.container.contained import Contained -from zope.app.container.contained import NameChooser -from zope.app.container.constraints import checkFactory -from zope.location.interfaces import ILocation -from zope.proxy import sameProxiedObjects -from zope.exceptions.interfaces import UserError - -from sqlos.interfaces import ISQLObject, ISQLObjectIsolated, IISQLObject -from sqlos.interfaces.container import ISQLObjectContainer -from sqlos.interfaces.container import IIsolatedSQLContainer - -def contained(obj, parent=None, name=None): - """An implementation of zope.app.container.contained.contained - that doesn't generate events, for internal use. - """ - if (parent is None): - raise TypeError, 'Must provide a parent' - - if not IContained.providedBy(obj): - if ILocation.providedBy(obj): - directlyProvides(obj, IContained, directlyProvidedBy(obj)) - else: - obj = ContainedProxy(obj) - - oldparent = obj.__parent__ - oldname = obj.__name__ - - if (oldparent is None) or not (oldparent is parent - or sameProxiedObjects(oldparent, parent)): - obj.__parent__ = parent - - if oldname != name and name is not None: - obj.__name__ = unicode(name) - - return obj - - -class SQLObjectNameChooser(NameChooser): - # XXX: This needs unit tests... - - def chooseName(self, name, obj): - if ISQLObject.providedBy(obj): - # Look for the SQLObject class our object is from, so get all - # allowed factories - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self.context, None, factory): - # get the sqlobject class - utility = zope.component.queryUtility(IISQLObject, name) - if utility is None: - continue - if obj.sqlmeta.table == utility.sqlmeta.table: - # if the tables names are the same, we assume that the - # sqlobject is an instance of that class, perhaps that - # is wrong - return "%s.%s" % (name, obj.id) - raise UserError("Cannot find a name") # XXX better message, i18n? - - -class SQLObjectContainer(Persistent, Contained): - - implements(ISQLObjectContainer) - - def __init__(self): - pass - - def _getAllowedIISQLObjectUtilities(self): - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self, None, factory): - utility = zope.component.queryUtility(IISQLObject, name) - # Someone might have registered a factory implementing - # IISQLObject using for whatever reason. - # in this case queryUtility returns None and we can just - # ignore it - if utility is not None: - yield name, utility - - def keys(self): - """ Return a sequence-like object containing the names - associated with the objects that appear in the folder - """ - for name, obj in self.items(): - yield name - - def __iter__(self): - return iter(self.keys()) - - def values(self): - """ Return a sequence-like object containing the objects that - appear in the folder. - """ - for name, obj in self.items(): - yield contained(obj, parent=self, name=name) - - def items(self): - """Return a sequence-like object containing tuples of the form - (name, object) for the objects that appear in the folder. - """ - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - for obj in utility.select(): - name = '%s.%s' % (utility_name, obj.id) - yield (name, contained(obj, parent=self, name=name)) - - def __getitem__(self, name): - """Return the named object. - - If the object is not found a KeyError is raised. - - lets get a container: - - >>> c = SQLObjectContainer() - - And make sure it doesn't bork on non-string values: - - >>> c[None] - Traceback (most recent call last): - ... - KeyError: ... - >>> c[object()] - Traceback (most recent call last): - ... - KeyError: ... - """ - if not isinstance(name, basestring): - raise KeyError, "%s is not a string" % name - try: - parts = name.split('.') - id = parts[-1] - factoryName = '.'.join(parts[:-1]) - except ValueError: - raise KeyError, name - - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - if factoryName != utility_name: - continue - try: - obj = utility.get(utility.sqlmeta.idType(id)) - return contained(obj, parent=self, name=name) - except (SQLObjectNotFound, ValueError): - # SQlObject raises ValueError if the key is not correct - raise KeyError, name - raise KeyError, name - - def get(self, name, default=None): - """Return the named object, or the value of the default - argument if given and the named object is not found. - If no default is given and the object is not found a - KeyError is raised. - """ - try: - return contained(self[name], parent=self, name=name) - except KeyError: - return default - - def __contains__(self, name): - """Return true if the named object appears in the folder.""" - return self.get(name, None) is not None - - def __len__(self): - """Return the number of objects in the folder.""" - i = 0 - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - i += utility.select().count() # optimal, does not get all objects - return i - - def __delitem__(self, name): - """Delete the named object from the container. - - Raises a KeyError if the object is not found. - """ - obj = self[name] - obj.destroySelf() - - def __setitem__(self, name, content): - return name - - -class SQLIsolatedContainer(SQLObjectContainer): - - implements(IIsolatedSQLContainer) - - _container_id = None - - def _getAllowedIISQLObjectUtilities(self): - # Ignore all utilities not implementing ISQLObjectIsolated - for name, utility in SQLObjectContainer._getAllowedIISQLObjectUtilities(self): - if ISQLObjectIsolated.implementedBy(utility): - yield name, utility - - def _getContainerId(self): - if self._container_id is None: - self._container_id = str(random.random()) # TODO better generation? - return self._container_id - def _setContainerId(self, value): - self._container_id = value - container_id = property(_getContainerId, _setContainerId) - - def __len__(self): - """Return the number of objects in the folder.""" - i = 0 - for name, utility in self._getAllowedIISQLObjectUtilities(): - i += utility.countByDomain(self.container_id) - return i - - def __delitem__(self, name): - """Delete the named object from the container. - - Raises a KeyError if the object is not found. - """ - obj = self[name] - domains = [d for d in obj.domains if d != self.container_id] - if not domains: - obj.destroySelf() - return - obj.domains = tuple(domains) - - def __setitem__(self, name, content): - domains = content.domains - if self.container_id not in domains: - content.domains = domains + (self.container_id, ) - return name - - def items(self): - """Return a sequence-like object containing tuples of the form - (name, object) for the objects that appear in the folder. - """ - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - for obj in utility.selectByDomain(self.container_id): - name = '%s.%s' % (utility_name, obj.id) - yield (name, contained(obj, parent=self, name=name)) - - def __getitem__(self, name): - obj = super(SQLIsolatedContainer, self).__getitem__(name) - if hasattr(obj, 'domains'): - if self.container_id in obj.domains: - return contained(obj, parent=self, name=name) - raise KeyError, name +from standard import contained, SQLObjectNameChooser, SQLObjectContainer +from isolated import SQLIsolatedContainer Copied: z3/sqlos/trunk/src/sqlos/container/isolated.py (from r36300, z3/sqlos/trunk/src/sqlos/container/__init__.py) ============================================================================== --- z3/sqlos/trunk/src/sqlos/container/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/container/isolated.py Mon Jan 8 19:15:03 2007 @@ -13,188 +13,12 @@ import random -from sqlobject import * -from persistent import Persistent from zope.interface import implements -from zope.component import IFactory -import zope.component -from zope.app.container.interfaces import IContained -from zope.app.container.contained import ContainedProxy -from zope.app.container.contained import Contained -from zope.app.container.contained import NameChooser -from zope.app.container.constraints import checkFactory -from zope.location.interfaces import ILocation -from zope.proxy import sameProxiedObjects -from zope.exceptions.interfaces import UserError -from sqlos.interfaces import ISQLObject, ISQLObjectIsolated, IISQLObject -from sqlos.interfaces.container import ISQLObjectContainer +from sqlos.container.standard import contained, SQLObjectContainer +from sqlos.interfaces import ISQLObjectIsolated from sqlos.interfaces.container import IIsolatedSQLContainer -def contained(obj, parent=None, name=None): - """An implementation of zope.app.container.contained.contained - that doesn't generate events, for internal use. - """ - if (parent is None): - raise TypeError, 'Must provide a parent' - - if not IContained.providedBy(obj): - if ILocation.providedBy(obj): - directlyProvides(obj, IContained, directlyProvidedBy(obj)) - else: - obj = ContainedProxy(obj) - - oldparent = obj.__parent__ - oldname = obj.__name__ - - if (oldparent is None) or not (oldparent is parent - or sameProxiedObjects(oldparent, parent)): - obj.__parent__ = parent - - if oldname != name and name is not None: - obj.__name__ = unicode(name) - - return obj - - -class SQLObjectNameChooser(NameChooser): - # XXX: This needs unit tests... - - def chooseName(self, name, obj): - if ISQLObject.providedBy(obj): - # Look for the SQLObject class our object is from, so get all - # allowed factories - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self.context, None, factory): - # get the sqlobject class - utility = zope.component.queryUtility(IISQLObject, name) - if utility is None: - continue - if obj.sqlmeta.table == utility.sqlmeta.table: - # if the tables names are the same, we assume that the - # sqlobject is an instance of that class, perhaps that - # is wrong - return "%s.%s" % (name, obj.id) - raise UserError("Cannot find a name") # XXX better message, i18n? - - -class SQLObjectContainer(Persistent, Contained): - - implements(ISQLObjectContainer) - - def __init__(self): - pass - - def _getAllowedIISQLObjectUtilities(self): - for name, factory in zope.component.getFactoriesFor(ISQLObject): - if checkFactory(self, None, factory): - utility = zope.component.queryUtility(IISQLObject, name) - # Someone might have registered a factory implementing - # IISQLObject using for whatever reason. - # in this case queryUtility returns None and we can just - # ignore it - if utility is not None: - yield name, utility - - def keys(self): - """ Return a sequence-like object containing the names - associated with the objects that appear in the folder - """ - for name, obj in self.items(): - yield name - - def __iter__(self): - return iter(self.keys()) - - def values(self): - """ Return a sequence-like object containing the objects that - appear in the folder. - """ - for name, obj in self.items(): - yield contained(obj, parent=self, name=name) - - def items(self): - """Return a sequence-like object containing tuples of the form - (name, object) for the objects that appear in the folder. - """ - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - for obj in utility.select(): - name = '%s.%s' % (utility_name, obj.id) - yield (name, contained(obj, parent=self, name=name)) - - def __getitem__(self, name): - """Return the named object. - - If the object is not found a KeyError is raised. - - lets get a container: - - >>> c = SQLObjectContainer() - - And make sure it doesn't bork on non-string values: - - >>> c[None] - Traceback (most recent call last): - ... - KeyError: ... - >>> c[object()] - Traceback (most recent call last): - ... - KeyError: ... - """ - if not isinstance(name, basestring): - raise KeyError, "%s is not a string" % name - try: - parts = name.split('.') - id = parts[-1] - factoryName = '.'.join(parts[:-1]) - except ValueError: - raise KeyError, name - - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - if factoryName != utility_name: - continue - try: - obj = utility.get(utility.sqlmeta.idType(id)) - return contained(obj, parent=self, name=name) - except (SQLObjectNotFound, ValueError): - # SQlObject raises ValueError if the key is not correct - raise KeyError, name - raise KeyError, name - - def get(self, name, default=None): - """Return the named object, or the value of the default - argument if given and the named object is not found. - If no default is given and the object is not found a - KeyError is raised. - """ - try: - return contained(self[name], parent=self, name=name) - except KeyError: - return default - - def __contains__(self, name): - """Return true if the named object appears in the folder.""" - return self.get(name, None) is not None - - def __len__(self): - """Return the number of objects in the folder.""" - i = 0 - for utility_name, utility in self._getAllowedIISQLObjectUtilities(): - i += utility.select().count() # optimal, does not get all objects - return i - - def __delitem__(self, name): - """Delete the named object from the container. - - Raises a KeyError if the object is not found. - """ - obj = self[name] - obj.destroySelf() - - def __setitem__(self, name, content): - return name - class SQLIsolatedContainer(SQLObjectContainer): Added: z3/sqlos/trunk/src/sqlos/container/standard.py ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/container/standard.py Mon Jan 8 19:15:03 2007 @@ -0,0 +1,192 @@ +############################################################################## +# +# Copyright (c) 2004 Enfold Systems LLC. All rights reserved. +# Copyright (c) 2005-2006 Brian Sutherland. All rights reserved. +# +# This software is distributed under the terms of the Zope Public +# License (ZPL) v2.1. See COPYING.txt for more information. +# +############################################################################## +""" +$Id: __init__.py 36300 2007-01-08 17:07:59Z kobold $ +""" + +from persistent import Persistent +from sqlobject import SQLObjectNotFound + +from zope.interface import implements, directlyProvides, directlyProvidedBy +from zope.app.container.interfaces import IContained +from zope.app.container.constraints import checkFactory +from zope.app.container.contained import NameChooser, ContainedProxy, Contained +from zope.component import IFactory, getFactoriesFor, queryUtility +from zope.exceptions.interfaces import UserError +from zope.location.interfaces import ILocation +from zope.proxy import sameProxiedObjects + +from sqlos.interfaces import ISQLObject, IISQLObject +from sqlos.interfaces.container import ISQLObjectContainer + + +def contained(obj, parent=None, name=None): + """An implementation of zope.app.container.contained.contained + that doesn't generate events, for internal use. + """ + if (parent is None): + raise TypeError, 'Must provide a parent' + + if not IContained.providedBy(obj): + if ILocation.providedBy(obj): + directlyProvides(obj, IContained, directlyProvidedBy(obj)) + else: + obj = ContainedProxy(obj) + + oldparent = obj.__parent__ + oldname = obj.__name__ + + if (oldparent is None) or not (oldparent is parent + or sameProxiedObjects(oldparent, parent)): + obj.__parent__ = parent + + if oldname != name and name is not None: + obj.__name__ = unicode(name) + + return obj + + +class SQLObjectNameChooser(NameChooser): + """NameChooser for sqlos containers""" + + def chooseName(self, name, obj): + if ISQLObject.providedBy(obj): + # Look for the SQLObject class our object is from, so get all + # allowed factories + for name, factory in getFactoriesFor(ISQLObject): + if checkFactory(self.context, None, factory): + # get the sqlobject class + utility = queryUtility(IISQLObject, name) + if utility is None: + continue + if obj.sqlmeta.table == utility.sqlmeta.table: + # if the tables names are the same, we assume that the + # sqlobject is an instance of that class, perhaps that + # is wrong + return "%s.%s" % (name, obj.id) + raise UserError("Cannot find a name") # XXX better message, i18n? + + +class SQLObjectContainer(Persistent, Contained): + + implements(ISQLObjectContainer) + + def __init__(self): + pass + + def _getAllowedIISQLObjectUtilities(self): + for name, factory in getFactoriesFor(ISQLObject): + if checkFactory(self, None, factory): + utility = queryUtility(IISQLObject, name) + # Someone might have registered a factory implementing + # IISQLObject using for whatever reason. + # in this case queryUtility returns None and we can just + # ignore it + if utility is not None: + yield name, utility + + def keys(self): + """ Return a sequence-like object containing the names + associated with the objects that appear in the folder + """ + for name, obj in self.items(): + yield name + + def __iter__(self): + return iter(self.keys()) + + def values(self): + """ Return a sequence-like object containing the objects that + appear in the folder. + """ + for name, obj in self.items(): + yield contained(obj, parent=self, name=name) + + def items(self): + """Return a sequence-like object containing tuples of the form + (name, object) for the objects that appear in the folder. + """ + for utility_name, utility in self._getAllowedIISQLObjectUtilities(): + for obj in utility.select(): + name = '%s.%s' % (utility_name, obj.id) + yield (name, contained(obj, parent=self, name=name)) + + def __getitem__(self, name): + """Return the named object. + + If the object is not found a KeyError is raised. + + lets get a container: + + >>> c = SQLObjectContainer() + + And make sure it doesn't bork on non-string values: + + >>> c[None] + Traceback (most recent call last): + ... + KeyError: ... + >>> c[object()] + Traceback (most recent call last): + ... + KeyError: ... + """ + if not isinstance(name, basestring): + raise KeyError, "%s is not a string" % name + try: + parts = name.split('.') + id = parts[-1] + factoryName = '.'.join(parts[:-1]) + except ValueError: + raise KeyError, name + + for utility_name, utility in self._getAllowedIISQLObjectUtilities(): + if factoryName != utility_name: + continue + try: + obj = utility.get(utility.sqlmeta.idType(id)) + return contained(obj, parent=self, name=name) + except (SQLObjectNotFound, ValueError): + # SQlObject raises ValueError if the key is not correct + raise KeyError, name + raise KeyError, name + + def get(self, name, default=None): + """Return the named object, or the value of the default + argument if given and the named object is not found. + If no default is given and the object is not found a + KeyError is raised. + """ + try: + return contained(self[name], parent=self, name=name) + except KeyError: + return default + + def __contains__(self, name): + """Return true if the named object appears in the folder.""" + return self.get(name, None) is not None + + def __len__(self): + """Return the number of objects in the folder.""" + i = 0 + for utility_name, utility in self._getAllowedIISQLObjectUtilities(): + i += utility.select().count() # optimal, does not get all objects + return i + + def __delitem__(self, name): + """Delete the named object from the container. + + Raises a KeyError if the object is not found. + """ + obj = self[name] + obj.destroySelf() + + def __setitem__(self, name, content): + return name Modified: z3/sqlos/trunk/src/sqlos/tests/test_doctests.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/test_doctests.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/test_doctests.py Mon Jan 8 19:15:03 2007 @@ -23,7 +23,8 @@ def test_suite(): return unittest.TestSuite([ - DocTestSuite('sqlos.container', optionflags=doctest.ELLIPSIS), + DocTestSuite('sqlos.container.standard', optionflags=doctest.ELLIPSIS), + DocTestSuite('sqlos.container.isolated', optionflags=doctest.ELLIPSIS), DocTestSuite('sqlos.connection'), DocTestSuite('sqlos._transaction'), DocTestSuite('sqlos.zsqlobject') From kobold at codespeak.net Mon Jan 8 20:13:18 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 20:13:18 +0100 (CET) Subject: [z3-checkins] r36313 - in z3/sqlos/trunk/src/sqlos: . container file file/tests ftests interfaces testing testing/tests tests Message-ID: <20070108191318.ED6CA10071@code0.codespeak.net> Author: kobold Date: Mon Jan 8 20:13:17 2007 New Revision: 36313 Added: z3/sqlos/trunk/src/sqlos/container/mono.py (contents, props changed) - copied, changed from r36307, z3/sqlos/trunk/src/sqlos/container/isolated.py z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt Modified: z3/sqlos/trunk/src/sqlos/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/_transaction.py (props changed) z3/sqlos/trunk/src/sqlos/adapter.py (props changed) z3/sqlos/trunk/src/sqlos/configure.zcml z3/sqlos/trunk/src/sqlos/connection.py (props changed) z3/sqlos/trunk/src/sqlos/container/__init__.py (contents, props changed) z3/sqlos/trunk/src/sqlos/container/isolated.py (props changed) z3/sqlos/trunk/src/sqlos/container/standard.py (props changed) z3/sqlos/trunk/src/sqlos/file/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/file/fsutility.py (props changed) z3/sqlos/trunk/src/sqlos/file/interfaces.py (props changed) z3/sqlos/trunk/src/sqlos/file/tests/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/file/tests/test_fsutility.py (props changed) z3/sqlos/trunk/src/sqlos/ftests/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py (contents, props changed) z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py (props changed) z3/sqlos/trunk/src/sqlos/interfaces/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/interfaces/auth.py (props changed) z3/sqlos/trunk/src/sqlos/interfaces/container.py (contents, props changed) z3/sqlos/trunk/src/sqlos/metaconfigure.py (props changed) z3/sqlos/trunk/src/sqlos/metadirectives.py (props changed) z3/sqlos/trunk/src/sqlos/testing/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/testing/sampleperson.py (contents, props changed) z3/sqlos/trunk/src/sqlos/testing/testdb.py (props changed) z3/sqlos/trunk/src/sqlos/testing/tests/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/testing/tests/test_sampleperson.py (props changed) z3/sqlos/trunk/src/sqlos/tests/__init__.py (props changed) z3/sqlos/trunk/src/sqlos/tests/test_doctests.py (contents, props changed) z3/sqlos/trunk/src/sqlos/tests/test_transaction.py (props changed) z3/sqlos/trunk/src/sqlos/tests/test_verify.py (props changed) z3/sqlos/trunk/src/sqlos/zsqlobject.py (props changed) Log: Implemented mono containers; ftests for mono containers. Modified: z3/sqlos/trunk/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/configure.zcml (original) +++ z3/sqlos/trunk/src/sqlos/configure.zcml Mon Jan 8 20:13:17 2007 @@ -39,6 +39,13 @@ factory=".container.SQLObjectNameChooser" /> + + >> c = SQLObjectMonoContainer() + >>> c.factory = "XXX" + + And make sure it doesn't bork on non-string values: + + >>> c[None] + Traceback (most recent call last): + ... + TypeError: Unable to look up ... + >>> c[object()] + Traceback (most recent call last): + ... + TypeError: Unable to look up ... + + """ + for utility_name, utility in self._getAllowedIISQLObjectUtilities(): + try: + obj = utility.get(utility.sqlmeta.idType(name)) return contained(obj, parent=self, name=name) + except (SQLObjectNotFound, ValueError): + raise KeyError, name raise KeyError, name Added: z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt Mon Jan 8 20:13:17 2007 @@ -0,0 +1,106 @@ +Functional test for SQLOSContainer objects +========================================== + +First, prepare the testing environment: + + >>> from sqlos.testing.sampleperson import SamplePerson, SamplePersonMonoContainer + >>> from sqlos.interfaces import ISQLObject + >>> from sqlos.interfaces.container import ISQLObjectMonoContainer + >>> from zope.interface.verify import verifyObject + >>> container = SamplePersonMonoContainer() + >>> verifyObject(ISQLObjectMonoContainer, container) + True + +We are not in the business of letting errors pass silently, so looking inside if +we get a database error (it must be of the type DatabaseException): + + >>> [i for i in container.items()] + Traceback (most recent call last): + ... + DatabaseException: ... + +So let's create some database tables if not already there: + + >>> from sqlos.testing.sampleperson import createTestingTables + >>> createTestingTables() + +We should now be able to look inside an empty container: + + >>> [i for i in container.keys()] + [] + >>> [i for i in container.items()] + [] + >>> [i for i in container] + [] + >>> [i for i in container.values()] + [] + >>> len(container) + 0 + +Lets create some objects: + + >>> people = [{'username': 'harry', + ... 'fullname': 'Harry the Hack', + ... 'password': 'harrypass'}, + ... {'username': 'sally', + ... 'fullname': 'Sally the Wack', + ... 'password': 'sallypass'}] + >>> from zope.app import zapi + >>> from sqlos.interfaces import IISQLObject + >>> SamplePerson = zapi.getUtility(IISQLObject, + ... u'sqlos.somename.SamplePerson', + ... context=container) + >>> harry = SamplePerson(**people[0]) + >>> len(container) + 1 + >>> sally = SamplePerson(**people[1]) + >>> len(container) + 2 + +Lets see whats inside: + + >>> [i[0] for i in container.items()] + [u'1', u'2'] + >>> [i[1] for i in container.items()] == [harry, sally] + True + >>> [i for i in container.values()] == [harry, sally] + True + >>> [i for i in container.keys()] + [u'1', u'2'] + >>> [i for i in container] + [u'1', u'2'] + +Let's test to see what the container does with bad id's (must raise KeyError): + + >>> container[3] + Traceback (most recent call last): + ... + KeyError: ... + +You can get() as well: + + >>> container.get(1) == harry + True + >>> container.get('sss', 'default') + 'default' + +Setitem passes but is really a no-op: + + >>> container['sss'] = 'yyy' + >>> len(container) + 2 + +Finally let's delete harry: + + >>> del container[1] + >>> len(container) + 1 + >>> container[1] + Traceback (most recent call last): + ... + KeyError: 1 + +CleanUp: + + >>> from sqlos.testing.sampleperson import dropTestingTables + >>> dropTestingTables() Modified: z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py (original) +++ z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py Mon Jan 8 20:13:17 2007 @@ -24,5 +24,6 @@ 'connection.txt', 'containers.txt', 'localutilities.txt', - 'isolated_containers.txt'] + 'isolated_containers.txt', + 'mono_containers.txt'] return FunctionalDocFileSuite(*filelist) Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/container.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/container.py Mon Jan 8 20:13:17 2007 @@ -15,19 +15,26 @@ from zope.app.container.interfaces import IContainerNamesContainer from zope.app.container.interfaces import IReadContainer, IContainer + class ISQLObjectReadContainer(IReadContainer, IAttributeAnnotatable): - """ An SQLObject Container """ + """An SQLObject Container """ + class ISQLObjectContainer(IContainer, IContainerNamesContainer, IAttributeAnnotatable): - """ An SQLObject Container """ + """An SQLObject Container """ def __setitem__(name, obj): """Add a new object""" __setitem__.precondition = ItemTypePrecondition() + class IIsolatedSQLContainer(ISQLObjectContainer): # TODO Attribute -> zope.schema.* - jinty container_id = Attribute("The id of the containers, this is a filter on the" "database table.") + + +class ISQLObjectMonoContainer(ISQLObjectContainer): + """An SQLObject Container """ Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/testing/sampleperson.py (original) +++ z3/sqlos/trunk/src/sqlos/testing/sampleperson.py Mon Jan 8 20:13:17 2007 @@ -7,7 +7,7 @@ from sqlos.zsqlobject import SQLOS from sqlos.interfaces import ISQLSchema, IISQLObjectIsolated, ISQLObjectIsolated from sqlos.interfaces.container import ISQLObjectContainer -from sqlos.container import SQLObjectContainer, SQLIsolatedContainer +from sqlos.container import SQLObjectContainer, SQLIsolatedContainer, SQLObjectMonoContainer def createTestingTablesSubscriber(obj): # An event subscriber that can be used to create the testing tables @@ -55,6 +55,12 @@ implements(IPersonContainer) +class SamplePersonMonoContainer(SQLObjectMonoContainer): + + implements(IPersonContainer) + factory = 'sqlos.somename.SamplePerson' + + class SamplePerson(SQLOS): implements(IPerson, IPersonContained) Modified: z3/sqlos/trunk/src/sqlos/tests/test_doctests.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/tests/test_doctests.py (original) +++ z3/sqlos/trunk/src/sqlos/tests/test_doctests.py Mon Jan 8 20:13:17 2007 @@ -25,6 +25,7 @@ return unittest.TestSuite([ DocTestSuite('sqlos.container.standard', optionflags=doctest.ELLIPSIS), DocTestSuite('sqlos.container.isolated', optionflags=doctest.ELLIPSIS), + DocTestSuite('sqlos.container.mono', optionflags=doctest.ELLIPSIS), DocTestSuite('sqlos.connection'), DocTestSuite('sqlos._transaction'), DocTestSuite('sqlos.zsqlobject') From ltucker at codespeak.net Mon Jan 8 20:19:08 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Mon, 8 Jan 2007 20:19:08 +0100 (CET) Subject: [z3-checkins] r36314 - in z3/deliverance/trunk/deliverance: . test-data Message-ID: <20070108191908.36D4410071@code0.codespeak.net> Author: ltucker Date: Mon Jan 8 20:19:05 2007 New Revision: 36314 Added: z3/deliverance/trunk/deliverance/test-data/test_inline_javascript.xml Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py z3/deliverance/trunk/deliverance/xslt.py Log: fix inline javascript with braces in xslt, remove print statements Added: z3/deliverance/trunk/deliverance/test-data/test_inline_javascript.xml ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/test-data/test_inline_javascript.xml Mon Jan 8 20:19:05 2007 @@ -0,0 +1,151 @@ + + + + + + + + + + + BlahDummy Content + + + + +
+ + + + + +
+ +

NEW: Daily email digests

+
+ + + + + + +
+ +
+ + + + + + + + + + + + +
+ + +
+ + + Blah + + + +
+ + + + + +
+ +

NEW: Daily email digests

+
+ + + + + + +
+ +
+ + + + + + + + + + + + +
+ +
+
+ + + + + + + + + BlahContent + + + + +
+ + + + + +
+ +

NEW: Daily email digests

+
+ + + + + + +
+ +
+ + + + + + + + + + + + +
+ + +
+ + + + + Content + + +
+ + + +
Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Mon Jan 8 20:19:05 2007 @@ -258,7 +258,6 @@ by using the wrapped WSGI application """ - print "get_internal_resource('%s')" % uri if 'paste.recursive.include' in in_environ: environ = in_environ['paste.recursive.include'].original_environ.copy() @@ -279,14 +278,12 @@ environ['QUERY_STRING'] = 'notheme' if 'HTTP_ACCEPT_ENCODING' in environ: - print "Knocking out ACCEPT_ENCODING: (%s)" % environ['HTTP_ACCEPT_ENCODING'] environ['HTTP_ACCEPT_ENCODING'] = '' if 'paste.recursive.include' in in_environ: # Try to do the redirect this way... includer = in_environ['paste.recursive.include'] res = includer(uri,environ) - print "did paste.recursive.include for %s: [%s]" % (uri,res.body) return res.body Modified: z3/deliverance/trunk/deliverance/xslt.py ============================================================================== --- z3/deliverance/trunk/deliverance/xslt.py (original) +++ z3/deliverance/trunk/deliverance/xslt.py Mon Jan 8 20:19:05 2007 @@ -72,6 +72,7 @@ self.fixup_links(theme_copy, theme_uri) self.xsl_escape_comments(theme_copy) + self.avt_escape(theme_copy) self.resolve_uri = reference_resolver if self.resolve_uri: @@ -466,5 +467,18 @@ del(rule.attrib[self.RULE_MOVE_KEY]) # just process it normally return + def avt_escape(self,elt): + """ + replaces all instances of { or } with {{ and }} to avoid + being interpreted as an Attribute Value Template by XSLT + """ + + for (k,v) in elt.attrib.items(): + escaped = v.replace('{','{{') + escaped = escaped.replace('}','}}') + elt.attrib[k] = escaped + + for child in elt: + self.avt_escape(child) From kobold at codespeak.net Mon Jan 8 20:48:55 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 20:48:55 +0100 (CET) Subject: [z3-checkins] r36315 - in z3/sqlos/trunk/src/sqlos: . container ftests interfaces testing Message-ID: <20070108194855.A8B0210071@code0.codespeak.net> Author: kobold Date: Mon Jan 8 20:48:54 2007 New Revision: 36315 Removed: z3/sqlos/trunk/src/sqlos/interfaces/auth.py Modified: z3/sqlos/trunk/src/sqlos/__init__.py z3/sqlos/trunk/src/sqlos/container/__init__.py z3/sqlos/trunk/src/sqlos/container/isolated.py z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt z3/sqlos/trunk/src/sqlos/interfaces/__init__.py z3/sqlos/trunk/src/sqlos/interfaces/container.py z3/sqlos/trunk/src/sqlos/testing/sampleperson.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: New API, with deprecation warnings for the old names: only isolated containeres are affected. Modified: z3/sqlos/trunk/src/sqlos/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/__init__.py Mon Jan 8 20:48:54 2007 @@ -13,4 +13,5 @@ from zope.deprecation import deprecated from sqlos.zsqlobject import SQLOS -deprecated('SQLOS', 'sqlos.SQLOS is deprecated and will go away in next release') +deprecated('SQLOS', 'sqlos.SQLOS is deprecated and will go away in next release; ' +'use sqlos.zsqlobject.SQLOS instead.') Modified: z3/sqlos/trunk/src/sqlos/container/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/container/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/container/__init__.py Mon Jan 8 20:48:54 2007 @@ -11,6 +11,13 @@ $Id$ """ +from zope.deprecation import deprecated + from standard import contained, SQLObjectNameChooser, SQLObjectContainer -from isolated import SQLIsolatedContainer +from isolated import SQLObjectIsolatedContainer from mono import SQLObjectMonoNameChooser, SQLObjectMonoContainer + +SQLIsolatedContainer = SQLObjectIsolatedContainer +deprecated('SQLIsolatedContainer', 'sqlos.container.SQLIsolatedContainer is deprecated ' +'and will go away in next release; use sqlos.container.SQLObjectIsolatedContainer ' +'instead.') Modified: z3/sqlos/trunk/src/sqlos/container/isolated.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/container/isolated.py (original) +++ z3/sqlos/trunk/src/sqlos/container/isolated.py Mon Jan 8 20:48:54 2007 @@ -17,12 +17,12 @@ from sqlos.container.standard import contained, SQLObjectContainer from sqlos.interfaces import ISQLObjectIsolated -from sqlos.interfaces.container import IIsolatedSQLContainer +from sqlos.interfaces.container import ISQLObjectIsolatedContainer -class SQLIsolatedContainer(SQLObjectContainer): +class SQLObjectIsolatedContainer(SQLObjectContainer): - implements(IIsolatedSQLContainer) + implements(ISQLObjectIsolatedContainer) _container_id = None @@ -75,7 +75,7 @@ yield (name, contained(obj, parent=self, name=name)) def __getitem__(self, name): - obj = super(SQLIsolatedContainer, self).__getitem__(name) + obj = super(SQLObjectIsolatedContainer, self).__getitem__(name) if hasattr(obj, 'domains'): if self.container_id in obj.domains: return contained(obj, parent=self, name=name) Modified: z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt Mon Jan 8 20:48:54 2007 @@ -1,11 +1,11 @@ First let's get a container for sqlos objects: >>> from sqlos.testing import sampleperson - >>> from sqlos.interfaces.container import IIsolatedSQLContainer + >>> from sqlos.interfaces.container import ISQLObjectIsolatedContainer >>> from zope.interface.verify import verifyObject >>> container = sampleperson.SampleIsolatedPersonContainer() - >>> verifyObject(IIsolatedSQLContainer, container) + >>> verifyObject(ISQLObjectIsolatedContainer, container) True We are not in the business of letting errors pass silently, so looking inside Modified: z3/sqlos/trunk/src/sqlos/interfaces/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/__init__.py Mon Jan 8 20:48:54 2007 @@ -23,235 +23,208 @@ from sqlobject.main import SQLObject, SelectResults from sqlobject.sqlbuilder import SQLObjectTable + class IConnectionName(Interface): """A marker interface for providing a connection name""" name = TextLine( title=u"Connection Name", - required=True + required=True, ) + class ISQLSchema(Interface): - """ SQLObject-based schemas must declare 'id' or else it doesn't - get security set """ + """Base interface for SQLObject-based objects""" id = Attribute('Id') + class IDBConnection(Interface): - """ SQLObject DBConnection interface """ + """SQLObject DBConnection interface""" - name = Attribute("The object name, used as key for caching connections") + name = Attribute("The object name, used as key for caching connections") debug = Attribute("Print debug trace messages") cache = Attribute("A ResultSet cache object") style = Attribute("A style object. Used for controlling the naming style.") + class IDBAPI(IDBConnection): + """DBAPI Interface""" def _runWithConnection(meth, *args): - """ Runs a method with a connection from the pool """ + """Runs a method with a connection from the pool""" def getConnection(): - """ Return a connection from the pool """ + """Return a connection from the pool""" def releaseConnection(conn): - """ Return a connection back to the pool """ + """Return a connection back to the pool""" def query(s): - """ Run a query string against the database """ + """Run a query string against the database""" def queryAll(s): - """ Run a query string against the database and return all results """ + """Run a query string against the database and return all results""" def queryOne(s): - """ Run a query string against the database and return one row """ + """Run a query string against the database and return one row""" def transaction(): - """ Return a transaction object for this connection """ + """Return a transaction object for this connection""" def queryInsertID(soInstance, id, names, values): - """ Insert a row into the database and return the generated id """ + """Insert a row into the database and return the generated id""" def iterSelect(select): - """ """ + """Iter on a select""" def queryForSelect(select): - """ """ + """Query for a select""" def createTable(soClass): - """ Create a table for the given SQLObject class """ + """Create a table for the given SQLObject class""" def createColumns(soClass): - """ Return a query needed to create the columns for the given SO class """ + """Return a query needed to create the columns for the given SO class""" def dropTable(tableName): - """ Drop the given table """ + """Drop the given table""" def clearTable(tableName): - """ Clear the given table """ - - ### Private methods + """Clear the given table""" def _SO_update(so, values): - """ """ + """""" def _SO_selectOne(so, columnNames): - """ """ + """""" def _SO_selectOneAlt(cls, columnNames, column, value): - """ """ + """""" def _SO_delete(so): - """ """ + """""" def _SO_selectJoin(soClass, column, value): - """ """ + """""" def _SO_intermediateJoin(table, getColumn, joinColumn, value): - """ """ + """""" def _SO_intermediateDelete(table, firstColumn, firstValue, secondColumn, secondValue): - """ """ + """""" def _SO_intermediateInsert(table, firstColumn, firstValue, secondColumn, secondValue): - """ """ + """""" def _SO_columnClause(soClass, kw): - """ """ + """""" + class ISQLConnection(IDBAPI): def makeConnection(): - """ Return a newly-built connection instance using args passed - on __init__ """ + """Return a newly-built connection instance""" def getConnection(): - """ Get a connection from the pool """ + """Get a connection from the pool""" def _runWithConnection(meth, *args): - """ Run a method with the give args and a connection """ + """Run a method with the give args and a connection""" def createColumn(soClass, column): - """ Create a column """ + """Create a column""" def createIDColumn(soClass): - """ Return the string used to create the ID column """ + """Return the string used to create the ID column""" def joinSQLType(join): - """ Return the string to be used in join queries """ + """Return the string to be used in join queries""" def tableExists(tableName): - """ Return if the given table exists or not in the database """ + """Return if the given table exists or not in the database""" def addColumn(tableName, column): - """ Add the given column to the database """ + """Add the given column to the database""" def delColumn(tableName, column): - """ Remove the given column from the database """ + """Remove the given column from the database""" def columnsFromSchema(tableName, soClass): - """ Return a set of columns from the table schema """ + """Return a set of columns from the table schema""" def guessClass(t): - """ Returns a column class and a dict to be used on column - initialization """ + """Returns a column class and a dict to be used on column initialization""" + class IZopeSQLConnection(ISQLConnection): - """ """ + """Marker for Zope relational database connections""" -class ISQLAttributeAnnotatable(IAttributeAnnotatable): - """ - Store annotations in the annotations table, keyed by - table_name/id on a IAttributeAnnotatable object. - """ class IReadSQLObjectClass(Interface): - q = Attribute('Query?') + q = Attribute('Query') def get(id): - """Return object by the given primary key. - """ + """Return object by the given primary key.""" def sqlrepr(value): - """ Shorthand for _connection.sqlrepr - """ + """Shorthand for _connection.sqlrepr""" def select(clause=None, clauseTables=None, orderBy=NoDefault, groupBy=None, limit=None, lazyColumns=False, reversed=False): - """ Do a select query and return the resulting objects. - """ + """Do a select query and return the resulting objects.""" def selectBy(**kw): - """ """ + """Do a select query filtering by the specified keywords""" + class IWriteSQLObjectClass(Interface): def delete(id): - """ Delete item by primary key id. - """ - - # XXX these moved to sqlmeta in sqlobject 0.7, should we define another - # interface? - #def addColumn(columnDef, changeSchema=False): - # """ Add a column to the class. If changeSchema is True, also - # add the column to the database. Also generates getter and - # setter methods on the class. - # """ - # - #def addColumnsFromDatabase(): - # """ Add to the class the columns that are defined on the - # database but not already present. - # """ - # - #def delColumn(column, changeSchema=False): - # """ Delete the given column from the class. 'column' may be - # either a string or a Col instance. If changeSchema is true, - # also remove the column from the database table. - # """ - # - #def addJoin(joinDef): - # """ Add a join definition to the class, optionally removing or - # adding items as requested. - # """ - # - #def delJoin(joinDef): - # """ Remove a join definition from the class, optionally - # removing or adding items as requested. - # """ + """Delete item by primary key id.""" def dropTable(ifExists=False, dropJoinTables=True): - """ Drop the table. If the 'ifExists' parameter was passed, - check if the table exists before trying to delete. If - 'dropJoinTables' is True, drop the join tables associated. + """Drop the table. + + If the 'ifExists' parameter was passed, check if the table exists + before trying to delete. If 'dropJoinTables' is True, drop the join + tables associated. """ def createTable(ifNotExists=False, createJoinTables=True): - """ Create the table. If the 'ifNotExists' parameter was - passed, check if the table exists before trying to create. If - 'createJoinTables' is True, create the join tables associated. + """Create the table. + + If the 'ifNotExists' parameter was passed, check if the table exists + before trying to create. If 'createJoinTables' is True, create the join + tables associated. """ + def createJoinTables(ifNotExists=False): - """ Create the associated join tables. If the 'ifNotExists' - parameter is True, check if the tables doesn't already exist - first. + """Create the associated join tables. + + If the 'ifNotExists' parameter is True, check if the tables doesn't + already exist first. """ def dropJoinTables(ifExists=False): - """ Drop the associated join tables. If the 'ifExists' - parameter is True, then first check if the tables exist before - trying to delete. + """Drop the associated join tables. + + If the 'ifExists' parameter is True, then first check if the tables + exist before trying to delete. """ def clearTable(): - """ Clear the table. - """ + """Clear the table.""" + class IISQLObject(IReadSQLObjectClass, IWriteSQLObjectClass): """Class methods for SQLObject classes.""" + class IISQLObjectIsolated(IISQLObject): """Support for using this class in isolated containers. @@ -268,34 +241,30 @@ similar to select() """ -class ISQLObject(IContained): - - # XXX - _idName moved to sqlmeta - #_idName = Attribute('Primary Key') +class ISQLObject(IContained): def set(**kw): - """ Used to update multiple values at once, potentially with - one SQL statement if possible. + """Used to update multiple values at once, potentially with one SQL + statement if possible. """ def destroySelf(): - """ Kill this object and remove it from all caches. Also - removes the row associated with this object from the table. + """Kill this object and remove it from all caches. Also removes the row + associated with this object from the table. """ def syncUpdate(): - """ Submit changes to the database - """ + """Submit changes to the database""" def sync(): - """ If there are pending changes, submit them to the database, - and after that re-sync the object with the database. + """If there are pending changes, submit them to the database, and after + that re-sync the object with the database. """ def expire(): - """ Expire the object, clearing the cache for the current connection. - """ + """Expire the object, clearing the cache for the current connection.""" + class ISQLObjectIsolated(ISQLObject): @@ -305,44 +274,44 @@ class ICacheSet(Interface): def get(id, cls): - """ Get object 'id' from cache key 'cls.__name__' """ + """Get object 'id' from cache key 'cls.__name__'""" def put(id, cls): - """ Set a cache value using 'id' and 'cls.__name__' as keys """ + """Set a cache value using 'id' and 'cls.__name__' as keys""" def finishPut(cls): - """ """ + """""" def created(id, cls, obj): - """ """ + """""" def purge(id, cls): - """ Purge an object from the cache """ + """Purge an object from the cache""" def clear(cls=None): - """ Clear the cache if cls is None, else clear only the given - class cache """ + """Clear the cache if cls is None, else clear only the given class cache""" + class ISelectResults(Interface): def __getitem__(item): - """ List Emulation """ + """List Emulation""" def __iter__(): - """ List Emulation """ + """List Emulation""" def __len__(): - """ List emulation """ + """List emulation""" + class IIterator(Interface): def __iter__(): - """ Iterator """ + """Iterator""" def next(): - """ Iterator """ + """Iterator""" -# XXX: Why do we need these???? defineChecker(SQLObjectTable, NoProxy) @@ -353,5 +322,3 @@ classImplements(_sybase.builder(), ISQLConnection) classImplements(SQLObject, ISQLObject) classImplements(SelectResults, ISelectResults) - - Deleted: /z3/sqlos/trunk/src/sqlos/interfaces/auth.py ============================================================================== --- /z3/sqlos/trunk/src/sqlos/interfaces/auth.py Mon Jan 8 20:48:54 2007 +++ (empty file) @@ -1,51 +0,0 @@ -############################################################################## -# -# Copyright (c) 2004 Enfold Systems LLC. All rights reserved. -# -# This software is distributed under the terms of the Zope Public -# License (ZPL) v2.1. See COPYING.txt for more information. -# -############################################################################## -""" -$Id: adapter.py 5212 2004-06-21 18:09:05Z philikon $ -""" - -from zope.interface import Interface -from zope.schema import TextLine, Password -from zope.app.container.constraints import ItemTypePrecondition -from zope.app.pluggableauth.interfaces import IPrincipalSource - -from sqlos.interfaces import ISQLSchema -from sqlos.interfaces.container import ISQLObjectContainer - -class IPrincipalInfo(ISQLSchema): - - fullname = TextLine(title=u'Full Name', - required=True) - - username = TextLine(title=u'Username', - description=u'The user login name', - required=True, - ) - -class IPrincipalPassword(Interface): - - password = Password(title=u'Password', - required=True) - -class IPrincipal(IPrincipalInfo, IPrincipalPassword): - """A SQL-Based Principal, for use with ISQLObjectPrincipalSource""" - -class ISQLObjectPrincipalSource(IPrincipalSource): - """Describes SQLObject-based principal sources.""" - - def readPrincipals(): - """ """ - -class IPrincipalContainer(ISQLObjectContainer): - """A container for principals""" - - def __setitem__(name, obj): - """Add a new object""" - - __setitem__.precondition = ItemTypePrecondition(IPrincipal) Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/container.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/container.py Mon Jan 8 20:48:54 2007 @@ -9,7 +9,10 @@ """ $Id: adapter.py 5212 2004-06-21 18:09:05Z philikon $ """ + from zope.interface import Attribute +from zope.deprecation import deprecated + from zope.app.container.constraints import ItemTypePrecondition from zope.annotation.interfaces import IAttributeAnnotatable from zope.app.container.interfaces import IContainerNamesContainer @@ -17,24 +20,32 @@ class ISQLObjectReadContainer(IReadContainer, IAttributeAnnotatable): - """An SQLObject Container """ + """Read interface for SQLObject containers""" -class ISQLObjectContainer(IContainer, IContainerNamesContainer, - IAttributeAnnotatable): - """An SQLObject Container """ +class ISQLObjectContainer(IContainer, IContainerNamesContainer, IAttributeAnnotatable): + """A SQLObject container""" def __setitem__(name, obj): """Add a new object""" - __setitem__.precondition = ItemTypePrecondition() + def __delitem__(name): + """Remove an object""" + -class IIsolatedSQLContainer(ISQLObjectContainer): - # TODO Attribute -> zope.schema.* - jinty - container_id = Attribute("The id of the containers, this is a filter on the" - "database table.") +class ISQLObjectIsolatedContainer(ISQLObjectContainer): + """An isolated SQLObject container""" + + container_id = Attribute(u'The id of the containers, this is a filter on ' + 'the database table.') class ISQLObjectMonoContainer(ISQLObjectContainer): - """An SQLObject Container """ + """A mono-type SQLObject container""" + + +IIsolatedSQLContainer = ISQLObjectIsolatedContainer +deprecated('IIsolatedSQLContainer', 'sqlos.interfaces.container.IIsolatedSQLContainer ' +'is deprecated and will go away in next release; use sqlos.interfaces.container.' +'ISQLObjectIsolatedContainer instead.') Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/testing/sampleperson.py (original) +++ z3/sqlos/trunk/src/sqlos/testing/sampleperson.py Mon Jan 8 20:48:54 2007 @@ -7,7 +7,7 @@ from sqlos.zsqlobject import SQLOS from sqlos.interfaces import ISQLSchema, IISQLObjectIsolated, ISQLObjectIsolated from sqlos.interfaces.container import ISQLObjectContainer -from sqlos.container import SQLObjectContainer, SQLIsolatedContainer, SQLObjectMonoContainer +from sqlos.container import SQLObjectContainer, SQLObjectIsolatedContainer, SQLObjectMonoContainer def createTestingTablesSubscriber(obj): # An event subscriber that can be used to create the testing tables @@ -113,7 +113,7 @@ selectByDomain = classmethod(selectByDomain) -class SampleIsolatedPersonContainer(SQLIsolatedContainer): +class SampleIsolatedPersonContainer(SQLObjectIsolatedContainer): implements(IPersonContainer) Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Mon Jan 8 20:48:54 2007 @@ -17,14 +17,16 @@ from sqlos.connection import ConnectionDescriptor from sqlos.interfaces import ISQLObject -from sqlos import _transaction +from sqlos._transaction import dirty_object_registry def syncUpdateAll(): """Calls syncUpdate on all dirty SQLOS objects, sending all SQL to the DB. >>> syncUpdateAll() + """ - _transaction.dirty_object_registry.syncUpdateAll() + + dirty_object_registry.syncUpdateAll() class SQLOS(SQLObject, Contained): @@ -48,8 +50,11 @@ And finally call tearDown and cleanup: >>> testdb.tearDown() + """ + implements(ISQLObject) + _connection = ConnectionDescriptor() class sqlmeta: @@ -57,12 +62,10 @@ def _set_dirty(self, value): if value: - _transaction.dirty_object_registry.register(self) + dirty_object_registry.register(self) self._dirty = value - def _get_dirty(self): return self._dirty - dirty = property(_get_dirty, _set_dirty) def get(self, id, connection=None, selectResults=None): @@ -72,7 +75,7 @@ # which has no __parent__ and thats not what you get. try: val = super(SQLOS, self).get(id, connection=connection, - selectResults=selectResults) + selectResults=selectResults) except ValueError: raise AttributeError, id if getattr(val, '__parent__', None) is not None: @@ -87,5 +90,4 @@ def setConnection(self, connection): if connection is not None: self._connection = connection - setConnection = classmethod(setConnection) From kobold at codespeak.net Mon Jan 8 22:26:54 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Mon, 8 Jan 2007 22:26:54 +0100 (CET) Subject: [z3-checkins] r36318 - in z3/sqlos/trunk/src/sqlos: . container ftests testing Message-ID: <20070108212654.7411910070@code0.codespeak.net> Author: kobold Date: Mon Jan 8 22:26:50 2007 New Revision: 36318 Modified: z3/sqlos/trunk/src/sqlos/container/mono.py z3/sqlos/trunk/src/sqlos/ftesting.zcml z3/sqlos/trunk/src/sqlos/ftests/adding.txt z3/sqlos/trunk/src/sqlos/ftests/connection.txt z3/sqlos/trunk/src/sqlos/ftests/containers.txt z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt z3/sqlos/trunk/src/sqlos/sampleapp.zcml z3/sqlos/trunk/src/sqlos/testing/sampleperson.py z3/sqlos/trunk/src/sqlos/testing/testdb.py Log: Improvements for the functional tests; fixed a bug in the mono containers. Modified: z3/sqlos/trunk/src/sqlos/container/mono.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/container/mono.py (original) +++ z3/sqlos/trunk/src/sqlos/container/mono.py Mon Jan 8 22:26:50 2007 @@ -19,6 +19,7 @@ from zope.app.container.contained import NameChooser from sqlos.container.standard import contained, SQLObjectContainer +from sqlos.interfaces import ISQLObject from sqlos.interfaces.container import ISQLObjectMonoContainer Modified: z3/sqlos/trunk/src/sqlos/ftesting.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftesting.zcml (original) +++ z3/sqlos/trunk/src/sqlos/ftesting.zcml Mon Jan 8 22:26:50 2007 @@ -27,7 +27,7 @@ dsn='dbi://:memory:' /> - + + + + + - - - + - @@ -83,7 +85,6 @@ interface="sqlos.testing.sampleperson.IPerson" set_schema="sqlos.testing.sampleperson.IPerson" /> - - + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + Modified: z3/sqlos/trunk/src/sqlos/ftests/adding.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/adding.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/adding.txt Mon Jan 8 22:26:50 2007 @@ -19,7 +19,7 @@ Go to the main interface and add a SQLObject MultiContainer >>> browser.open('http://localhost/manage') - >>> 'SamplePerson' not in str(browser.contents) + >>> 'sqlos.testing.SamplePerson' not in str(browser.contents) True >>> browser.getLink('SQLObject Multi Container').click() >>> browser.getControl(name='new_value').value = 'multicontainer1' @@ -36,7 +36,7 @@ Lets try editing bob: - >>> browser.getLink('sqlos.somename.SamplePerson.1').click() + >>> browser.getLink('sqlos.testing.SamplePerson.1').click() >>> print browser.contents >> browser.getLink('multicontainer1').click() >>> page = browser.url - >>> 'sqlos.somename.SamplePerson.1' in browser.contents + >>> 'sqlos.testing.SamplePerson.1' in browser.contents True >>> ctrl = browser.getControl(name='ids:list') - >>> ctrl.value = ['sqlos.somename.SamplePerson.1'] + >>> ctrl.value = ['sqlos.testing.SamplePerson.1'] >>> browser.getControl('Delete').click() >>> browser.open(page) - >>> 'sqlos.somename.SamplePerson.1' in browser.contents + >>> 'sqlos.testing.SamplePerson.1' in browser.contents + False + +Now, go to the main interface and add a SQLObject Mono Container: + + >>> browser.open('http://localhost/manage') + >>> 'sqlos.testing.SamplePerson' not in str(browser.contents) + True + >>> browser.getLink('SQLObject Mono Container').click() + >>> browser.getControl(name='new_value').value = 'monocontainer1' + >>> browser.getControl('Apply').click() + +Now add a Sample Person to the container: + + >>> browser.getLink('monocontainer1').click() + >>> browser.getLink('SamplePerson').click() + >>> browser.getControl(name='field.fullname').value = 'Boe' + >>> browser.getControl(name='field.username').value = 'boe' + >>> browser.getControl(name='field.password').value = 'ebo' + >>> browser.getControl('Add').click() + +Lets try editing boe: + + >>> browser.open('http://localhost/monocontainer1/1/@@edit.html') + >>> print browser.contents + >> browser.getControl(name='field.fullname').value = 'Beo Bones' + >>> browser.getControl('Change').click() + >>> print browser.contents + >> browser.getLink('monocontainer1').click() + >>> page = browser.url + >>> '"1/@@Selected' in browser.contents + True + >>> ctrl = browser.getControl(name='ids:list') + >>> ctrl.value = ['1'] + >>> browser.getControl('Delete').click() + >>> browser.open(page) + >>> '"1/@@Selected' in browser.contents False CleanUp: Modified: z3/sqlos/trunk/src/sqlos/ftests/connection.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/connection.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/connection.txt Mon Jan 8 22:26:50 2007 @@ -43,7 +43,7 @@ >>> from zope.app import zapi >>> from sqlos.interfaces import IISQLObject >>> SamplePerson = zapi.getUtility(IISQLObject, - ... u'sqlos.somename.SamplePerson') + ... u'sqlos.testing.SamplePerson') >>> harry = SamplePerson(username='h', fullname='H', password='p') >>> tmpcon = harry._connection Modified: z3/sqlos/trunk/src/sqlos/ftests/containers.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/containers.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/containers.txt Mon Jan 8 22:26:50 2007 @@ -19,7 +19,7 @@ In the ftesting.zcml a SamplePerson factory should be registered: >>> utilities = [i for i, j in container._getAllowedIISQLObjectUtilities()] - >>> u'sqlos.somename.SamplePerson' in utilities + >>> u'sqlos.testing.SamplePerson' in utilities True So let's create some database tables if not already there: @@ -50,7 +50,7 @@ >>> from zope.app import zapi >>> from sqlos.interfaces import IISQLObject >>> SamplePerson = zapi.getUtility(IISQLObject, - ... u'sqlos.somename.SamplePerson', + ... u'sqlos.testing.SamplePerson', ... context=container) >>> harry = SamplePerson(**people[0]) >>> len(container) @@ -62,20 +62,20 @@ Lets see whats inside: >>> [i[0] for i in container.items()] - [u'sqlos.somename.SamplePerson.1', u'sqlos.somename.SamplePerson.2'] + [u'sqlos.testing.SamplePerson.1', u'sqlos.testing.SamplePerson.2'] >>> [i[1] for i in container.items()] == [harry, sally] True >>> [i for i in container.values()] == [harry, sally] True >>> [i for i in container.keys()] - [u'sqlos.somename.SamplePerson.1', u'sqlos.somename.SamplePerson.2'] + [u'sqlos.testing.SamplePerson.1', u'sqlos.testing.SamplePerson.2'] >>> [i for i in container] - [u'sqlos.somename.SamplePerson.1', u'sqlos.somename.SamplePerson.2'] + [u'sqlos.testing.SamplePerson.1', u'sqlos.testing.SamplePerson.2'] Notice that the keys are generated from the name of the factory and the id of the instance: - >>> harry_name = 'sqlos.somename.SamplePerson.%s' % harry.id + >>> harry_name = 'sqlos.testing.SamplePerson.%s' % harry.id >>> container[harry_name] == harry True >>> container[unicode(harry_name)] == harry @@ -83,7 +83,7 @@ Let's test to see what the container does with bad id's (must raise KeyError): - >>> container['sqlos.somename.SamplePerson.oops'] + >>> container['sqlos.testing.SamplePerson.oops'] Traceback (most recent call last): ... KeyError: ... @@ -109,7 +109,7 @@ >>> container[harry_name] Traceback (most recent call last): ... - KeyError: 'sqlos.somename.SamplePerson.1' + KeyError: 'sqlos.testing.SamplePerson.1' Lets make another container which can have dogs as well as people: @@ -124,7 +124,7 @@ Now we add sally's dog: - >>> fido = sampleperson.Dog(fullname='Fido', owner='sally') + >>> fido = sampleperson.SampleDog(fullname='Fido', owner='sally') >>> len(multicontainer) 2 >>> fido in [i for i in multicontainer.values()] Modified: z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/isolated_containers.txt Mon Jan 8 22:26:50 2007 @@ -19,7 +19,7 @@ In the ftesting.zcml a SampleIsolatedPerson factory should be registered: >>> utilities = [i for i, j in container._getAllowedIISQLObjectUtilities()] - >>> u'SampleIsolatedPerson' in utilities + >>> u'sqlos.testing.SampleIsolatedPerson' in utilities True So let's create some database tables if not already there: @@ -53,7 +53,7 @@ >>> from zope.app import zapi >>> from sqlos.interfaces import IISQLObject >>> SampleIsolatedPerson = zapi.getUtility(IISQLObject, - ... u'SampleIsolatedPerson', + ... u'sqlos.testing.SampleIsolatedPerson', ... context=container) >>> harry = SampleIsolatedPerson(**people[0]) >>> sally = SampleIsolatedPerson(**people[1]) @@ -71,20 +71,20 @@ Lets see whats inside: >>> [i[0] for i in container.items()] - [u'SampleIsolatedPerson.1', u'SampleIsolatedPerson.2'] + [u'sqlos.testing.SampleIsolatedPerson.1', u'sqlos.testing.SampleIsolatedPerson.2'] >>> [i[1] for i in container.items()] == [harry, sally] True >>> [i for i in container.values()] == [harry, sally] True >>> [i for i in container.keys()] - [u'SampleIsolatedPerson.1', u'SampleIsolatedPerson.2'] + [u'sqlos.testing.SampleIsolatedPerson.1', u'sqlos.testing.SampleIsolatedPerson.2'] >>> [i for i in container] - [u'SampleIsolatedPerson.1', u'SampleIsolatedPerson.2'] + [u'sqlos.testing.SampleIsolatedPerson.1', u'sqlos.testing.SampleIsolatedPerson.2'] Notice that the keys are generated from the name of the factory and the id of the instance: - >>> harry_name = 'SampleIsolatedPerson.%s' % harry.id + >>> harry_name = 'sqlos.testing.SampleIsolatedPerson.%s' % harry.id >>> container[harry_name] == harry True >>> container[unicode(harry_name)] == harry Modified: z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt Mon Jan 8 22:26:50 2007 @@ -58,7 +58,7 @@ >>> cursor = localUtility().cursor() >>> c = cursor.execute( - ... '''create table dog ( + ... '''create table sample_dog ( ... id integer primary key, ... fullname varchar(50) not null, ... owner varchar(20) not null)''') Modified: z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/mono_containers.txt Mon Jan 8 22:26:50 2007 @@ -48,7 +48,7 @@ >>> from zope.app import zapi >>> from sqlos.interfaces import IISQLObject >>> SamplePerson = zapi.getUtility(IISQLObject, - ... u'sqlos.somename.SamplePerson', + ... u'sqlos.testing.SamplePerson', ... context=container) >>> harry = SamplePerson(**people[0]) >>> len(container) Modified: z3/sqlos/trunk/src/sqlos/sampleapp.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/sampleapp.zcml (original) +++ z3/sqlos/trunk/src/sqlos/sampleapp.zcml Mon Jan 8 22:26:50 2007 @@ -1,11 +1,11 @@ - - + - + Author: ltucker Date: Tue Jan 9 17:19:11 2007 New Revision: 36376 Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/controllers/remote_getter.py z3/deliverance/DeliveranceDemo/trunk/setup.py Log: wsgiremote -> httpencode Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/controllers/remote_getter.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/ddemo/controllers/remote_getter.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/ddemo/controllers/remote_getter.py Tue Jan 9 17:19:11 2007 @@ -1,7 +1,7 @@ from ddemo.controllers import * -from wsgiremote.lxmlformat import xml as lxml_format -from wsgiremote.json import json as json_format -from wsgiremote import BadRequestError +from httpencode.lxmlformat import xml as lxml_format +from httpencode.json import json as json_format +from httpencode import BadRequestError class RemoteGetterController(BaseController): Modified: z3/deliverance/DeliveranceDemo/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/setup.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/setup.py Tue Jan 9 17:19:11 2007 @@ -12,7 +12,7 @@ "Pylons>=0.9.3", 'Deliverance', 'WSGIFilter', - 'WSGIRemote', + 'HTTPEncode', ], packages=find_packages(), include_package_data=True, From ianb at codespeak.net Tue Jan 9 23:20:37 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Tue, 9 Jan 2007 23:20:37 +0100 (CET) Subject: [z3-checkins] r36391 - in z3/deliverance/trunk/deliverance: . test-data/static Message-ID: <20070109222037.E63C210071@code0.codespeak.net> Author: ianb Date: Tue Jan 9 23:20:32 2007 New Revision: 36391 Added: z3/deliverance/trunk/deliverance/test-data/static/example with spaces.html - copied unchanged from r35754, z3/deliverance/trunk/deliverance/test-data/static/example.html z3/deliverance/trunk/deliverance/test-data/static/standardrules with spaces.xml - copied unchanged from r35754, z3/deliverance/trunk/deliverance/test-data/static/standardrules.xml z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules with spaces.xml - copied, changed from r35754, z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules.xml Modified: z3/deliverance/trunk/deliverance/test_wsgi.py Log: Added tests that include spaces Copied: z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules with spaces.xml (from r35754, z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules.xml) ============================================================================== --- z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules.xml (original) +++ z3/deliverance/trunk/deliverance/test-data/static/xinclude_rules with spaces.xml Tue Jan 9 23:20:32 2007 @@ -1,7 +1,7 @@ - + Modified: z3/deliverance/trunk/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/trunk/deliverance/test_wsgi.py (original) +++ z3/deliverance/trunk/deliverance/test_wsgi.py Tue Jan 9 23:20:32 2007 @@ -90,6 +90,18 @@ res2 = app.get('/xinclude_expected.html?notheme') html_string_compare(res.body, res2.body) +def do_with_spaces(renderer_type, name): + wsgi_app = DeliveranceMiddleware(static_app, 'xinclude_theme.html', 'xinclude_rules.xml', + renderer_type) + app = TestApp(wsgi_app) + expected = app.get('/xinclude_expected.html?notheme').body + res = app.get('/example%20with%20spaces.html') + html_string_compare(res.body, expected) + wsgi_app = DeliveranceMiddleware(static_app, 'xinclude_theme.html', 'xinclude_rules%20with%20spaces.xml', + renderer_type) + app = TestApp(wsgi_app) + res2 = app.get('/example.html') + html_string_compare(res2.body, expected) def do_nycsr(renderer_type, name): wsgi_app = DeliveranceMiddleware(nycsr_app, 'http://codespeak.net/svn/z3/deliverance/trunk/deliverance/test-data/nycsr/nycsr_theme.html','nycsr.xml', @@ -137,7 +149,7 @@ html_string_compare(res.body, res2.body) RENDERER_TYPES = ['py', 'xslt'] -TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate ] +TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate ] def test_all(): for renderer_type in RENDERER_TYPES: for test_func in TEST_FUNCS: From kobold at codespeak.net Wed Jan 10 20:31:38 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Wed, 10 Jan 2007 20:31:38 +0100 (CET) Subject: [z3-checkins] r36434 - in z3/sqlos/trunk/src/sqlos: . container ftests interfaces testing Message-ID: <20070110193138.58D2E1007C@code0.codespeak.net> Author: kobold Date: Wed Jan 10 20:31:36 2007 New Revision: 36434 Added: z3/sqlos/trunk/src/sqlos/ftests/joins.txt Modified: z3/sqlos/trunk/src/sqlos/configure.zcml z3/sqlos/trunk/src/sqlos/container/standard.py z3/sqlos/trunk/src/sqlos/ftesting.zcml z3/sqlos/trunk/src/sqlos/ftests/adding.txt z3/sqlos/trunk/src/sqlos/ftests/containers.txt z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py z3/sqlos/trunk/src/sqlos/interfaces/__init__.py z3/sqlos/trunk/src/sqlos/interfaces/container.py z3/sqlos/trunk/src/sqlos/testing/sampleperson.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Implemented native joins between SQLOS objects. Modified: z3/sqlos/trunk/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/configure.zcml (original) +++ z3/sqlos/trunk/src/sqlos/configure.zcml Wed Jan 10 20:31:36 2007 @@ -4,33 +4,7 @@ xmlns:browser="http://namespaces.zope.org/browser" i18n_domain="sqlos"> - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + - + + + >> sampleperson.dropTestingTables() Modified: z3/sqlos/trunk/src/sqlos/ftests/containers.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/containers.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/containers.txt Wed Jan 10 20:31:36 2007 @@ -124,11 +124,15 @@ Now we add sally's dog: - >>> fido = sampleperson.SampleDog(fullname='Fido', owner='sally') + >>> fido = sampleperson.SampleDog(fullname='Fido', owner=sally) >>> len(multicontainer) 2 >>> fido in [i for i in multicontainer.values()] True + >>> fido in sally.dogs + True + >>> fido.owner is sally + True CleanUp: Added: z3/sqlos/trunk/src/sqlos/ftests/joins.txt ============================================================================== --- (empty file) +++ z3/sqlos/trunk/src/sqlos/ftests/joins.txt Wed Jan 10 20:31:36 2007 @@ -0,0 +1,127 @@ +================= +Joined SQLObjects +================= + +Test join relationships between SQLOS objects. + +First lets set up some tables in the database: + + >>> from sqlos.testing import sampleperson + >>> sampleperson.createTestingTables() + +Then get a browser: + + >>> from zope.testbrowser.testing import Browser + >>> browser = Browser() + >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw') + >>> browser.handleErrors = False + +Go to the main interface and add a SQLObject MultiContainer + + >>> browser.open('http://localhost/manage') + >>> 'sqlos.testing.SamplePerson' not in str(browser.contents) + True + >>> browser.getLink('SQLObject Multi Container').click() + >>> browser.getControl(name='new_value').value = 'multicontainer1' + >>> browser.getControl('Apply').click() + +Now add a Sample Person to the container: + + >>> browser.getLink('multicontainer1').click() + >>> browser.getLink('SamplePerson').click() + >>> browser.getControl(name='field.fullname').value = 'Bob' + >>> browser.getControl(name='field.username').value = 'bob' + >>> browser.getControl(name='field.password').value = 'obo' + >>> browser.getControl('Add').click() + +Lets try editing bob: + + >>> browser.getLink('sqlos.testing.SamplePerson.1').click() + >>> print browser.contents + >> browser.getControl(name='field.fullname').value = 'Bobby Bones' + >>> browser.getControl('Change').click() + >>> print browser.contents + >> from sqlos.testing.sampleperson import SamplePerson, SampleDog + >>> bob = SamplePerson.get(1) + >>> dog = SampleDog(fullname='Fido', owner=bob) + +Verify the interfaces: + + >>> from zope.interface.verify import verifyObject + >>> from zope.app.container.interfaces import IReadContainer + >>> from sqlos.interfaces.container import ISQLObjectJoinContainer + >>> verifyObject(IReadContainer, bob) + True + >>> verifyObject(ISQLObjectJoinContainer, bob['dogs']) + True + +Let's try to access to the dog going through the owner: + + >>> browser.getLink('Contents').click() + >>> 'dogs' in browser.contents + True + >>> browser.getLink('dogs').click() + >>> '1/@@' in browser.contents + True + >>> browser.open('http://localhost/multicontainer1/sqlos.testing.SamplePerson.1/' + ... 'dogs/1') + >>> browser.getControl(name='field.fullname').value = 'Fido dog' + >>> browser.getControl('Change').click() + >>> print browser.contents + >> browser.getLink('multicontainer1').click() + >>> browser.getLink('sqlos.testing.SampleDog.1').click() + >>> browser.getLink('Contents').click() + >>> 'owner' in browser.contents + True + >>> browser.getLink('owner').click() + >>> browser.getControl(name='field.fullname').value = 'Bob the top' + >>> browser.getControl('Change').click() + >>> print browser.contents + >> sampleperson.dropTestingTables() Modified: z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt (original) +++ z3/sqlos/trunk/src/sqlos/ftests/localutilities.txt Wed Jan 10 20:31:36 2007 @@ -61,7 +61,7 @@ ... '''create table sample_dog ( ... id integer primary key, ... fullname varchar(50) not null, - ... owner varchar(20) not null)''') + ... owner_id int not null)''') >>> c = cursor.execute( ... '''create table sample_isolated_person ( ... id integer primary key, Modified: z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py (original) +++ z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py Wed Jan 10 20:31:36 2007 @@ -25,5 +25,6 @@ 'containers.txt', 'localutilities.txt', 'isolated_containers.txt', - 'mono_containers.txt'] + 'mono_containers.txt', + 'joins.txt'] return FunctionalDocFileSuite(*filelist) Modified: z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py (original) +++ z3/sqlos/trunk/src/sqlos/ftests/test_transaction.py Wed Jan 10 20:31:36 2007 @@ -20,7 +20,7 @@ from zope.rdb.interfaces import IZopeDatabaseAdapter from sqlos.interfaces import IConnectionName -from sqlos.testing.sampleperson import SamplePerson +from sqlos.testing.sampleperson import SamplePerson, SampleDog __metaclass__ = type @@ -29,6 +29,7 @@ def setUp(self): super(TestTransaction, self).setUp() SamplePerson.createTable(ifNotExists=True) + SampleDog.createTable(ifNotExists=True) person = SamplePerson(fullname='Sidnei da Silva', username='sidnei', password='test') Modified: z3/sqlos/trunk/src/sqlos/interfaces/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/__init__.py Wed Jan 10 20:31:36 2007 @@ -14,7 +14,7 @@ from zope.interface import classImplements from zope.security.checker import NamesChecker, NoProxy, defineChecker from zope.schema.vocabulary import SimpleVocabulary -from zope.schema import Choice, List +from zope.schema import Choice, List, TextLine from zope.annotation.interfaces import IAttributeAnnotatable from zope.app.container.interfaces import IContained from sqlobject import NoDefault @@ -33,10 +33,13 @@ ) -class ISQLSchema(Interface): +class ISQLSchema(IContained): """Base interface for SQLObject-based objects""" - id = Attribute('Id') + id = Attribute(u'Id') + + def setParent(parent): + """Set the object's parent""" class IDBConnection(Interface): @@ -242,7 +245,9 @@ """ -class ISQLObject(IContained): +class ISQLObject(Interface): + + sqlmeta = Attribute(u'Subclass sqlmeta for SQLObject objects') def set(**kw): """Used to update multiple values at once, potentially with one SQL @@ -303,6 +308,63 @@ def __len__(): """List emulation""" + def clone(**newOps): + """""" + + def orderBy(orderBy): + """""" + + def connection(conn): + """""" + + def limit(limit): + """""" + + def lazyColumns(value): + """""" + + def reversed(): + """""" + + def distinct(): + """""" + + def newClause(new_clause): + """""" + + def filter(filter_clause): + """""" + + def __getitem__(value): + """""" + + def lazyIter(): + """""" + + def accumulate(*expressions): + """""" + + def count(): + """""" + + def accumulateMany(*attributes): + """""" + + def accumulateOne(func_name, attribute): + """""" + + def sum(attribute): + """""" + + def min(attribute): + """""" + + def avg(attribute): + """""" + + def max(attribute): + """""" + class IIterator(Interface): Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/container.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/container.py Wed Jan 10 20:31:36 2007 @@ -11,19 +11,17 @@ """ from zope.interface import Attribute +from zope.interface.common.mapping import IEnumerableMapping + from zope.deprecation import deprecated from zope.app.container.constraints import ItemTypePrecondition from zope.annotation.interfaces import IAttributeAnnotatable -from zope.app.container.interfaces import IContainerNamesContainer -from zope.app.container.interfaces import IReadContainer, IContainer - - -class ISQLObjectReadContainer(IReadContainer, IAttributeAnnotatable): - """Read interface for SQLObject containers""" +from zope.app.container.interfaces import IContainerNamesContainer, IReadContainer, \ + IContainer, IContained -class ISQLObjectContainer(IContainer, IContainerNamesContainer, IAttributeAnnotatable): +class ISQLObjectContainer(IContainerNamesContainer, IAttributeAnnotatable): """A SQLObject container""" def __setitem__(name, obj): @@ -45,6 +43,10 @@ """A mono-type SQLObject container""" +class ISQLObjectJoinContainer(IContainerNamesContainer, IEnumerableMapping, IContained): + """A SQLObject container for joins""" + + IIsolatedSQLContainer = ISQLObjectIsolatedContainer deprecated('IIsolatedSQLContainer', 'sqlos.interfaces.container.IIsolatedSQLContainer ' 'is deprecated and will go away in next release; use sqlos.interfaces.container.' Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/testing/sampleperson.py (original) +++ z3/sqlos/trunk/src/sqlos/testing/sampleperson.py Wed Jan 10 20:31:36 2007 @@ -13,7 +13,7 @@ from sqlobject import * import transaction -from zope.interface import implements, classProvides, Interface +from zope.interface import implements, classProvides, Interface, Attribute from zope.schema import TextLine, Datetime from zope.app.container import constraints from zope.app.container.interfaces import IContained @@ -68,6 +68,8 @@ required=True, ) + dogs = Attribute(u'List of dogs') + class IPersonContainer(ISQLObjectContainer): @@ -97,6 +99,7 @@ fullname = StringCol(length=50, notNull=1) username = StringCol(length=20, notNull=1) password = StringCol(length=20, notNull=1) + dogs = SQLMultipleJoin('SampleDog', joinColumn='owner_id') class SampleIsolatedPerson(SQLOS): @@ -167,7 +170,7 @@ implements(IDog) fullname = StringCol(length=50, notNull=1) - owner = StringCol(length=20, notNull=1) + owner = ForeignKey('SamplePerson', notNull=1, cascade=True) class IMultiContainer(IPersonContainer): Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Wed Jan 10 20:31:36 2007 @@ -10,15 +10,25 @@ $Id: __init__.py 5216 2004-06-21 18:33:07Z dreamcatcher $ """ +import sqlobject + from zope.interface import implements from sqlobject.main import SQLObject -from sqlobject import StringCol + from zope.app.container.contained import Contained +from zope.app.container.interfaces import IReadContainer +from zope.component import adapts +from zope.security.proxy import removeSecurityProxy +from zope.traversing.interfaces import ITraversable +from zope.traversing.adapters import _marker from sqlos.connection import ConnectionDescriptor -from sqlos.interfaces import ISQLObject +from sqlos.container import contained +from sqlos.interfaces import ISQLObject, ISelectResults +from sqlos.interfaces.container import ISQLObjectJoinContainer from sqlos._transaction import dirty_object_registry + def syncUpdateAll(): """Calls syncUpdate on all dirty SQLOS objects, sending all SQL to the DB. @@ -46,6 +56,9 @@ >>> from zope.interface.verify import verifyObject >>> verifyObject(ISQLObject, s) True + >>> verifyObject(IReadContainer, s) + True + And finally call tearDown and cleanup: @@ -53,9 +66,10 @@ """ - implements(ISQLObject) + implements(ISQLObject, IReadContainer) _connection = ConnectionDescriptor() + _containers = None class sqlmeta: lazyUpdate = True @@ -69,19 +83,11 @@ dirty = property(_get_dirty, _set_dirty) def get(self, id, connection=None, selectResults=None): - # While interacting with zope, we may end up having - # objects in the cache that have a __parent__ set. - # This may be confusing when expect to get a object - # which has no __parent__ and thats not what you get. try: - val = super(SQLOS, self).get(id, connection=connection, + return super(SQLOS, self).get(id, connection=connection, selectResults=selectResults) except ValueError: raise AttributeError, id - if getattr(val, '__parent__', None) is not None: - val.__parent__ = None - val.__name__ = None - return val get = classmethod(get) def __repr__(self): @@ -91,3 +97,162 @@ if connection is not None: self._connection = connection setConnection = classmethod(setConnection) + + def setParent(self, parent): + self.__parent__ = parent + + def __getitem__(self, name): + """See zope.app.container.interfaces.IReadContainer""" + if self._containers is None: + self._containers = {} + elif name in self._containers: + return self._containers[name] + + obj = None + sqlmeta = self.sqlmeta + for column in sqlmeta.columns: + if column.endswith('ID') and \ + sqlmeta.columns[column].foreignName == name and \ + isinstance(sqlmeta.columns[column], sqlobject.SOForeignKey): + obj = contained(getattr(self, sqlmeta.columns[column].foreignName), + parent=self, name=name) + + if obj is not None: + self._containers[name] = obj + return obj + + for j in sqlmeta.joins: + if j.joinDef.name == name: + obj = getattr(self, name) + if ISelectResults.providedBy(obj): + obj = contained(ISQLObjectJoinContainer(obj), parent=self, name=name) + elif not ISQLObject.providedBy(obj): + obj = None + + if obj is not None: + self._containers[name] = obj + return obj + + raise KeyError, name + + def __contains__(self, name): + """See zope.app.container.interfaces.IReadContainer""" + return name in self.values() + + def __iter__(self): + """See zope.app.container.interfaces.IReadContainer""" + for i in self.keys(): + yield i + + def __len__(self): + """See zope.app.container.interfaces.IReadContainer""" + return len(tuple(self.keys())) + + def keys(self): + """See zope.app.container.interfaces.IReadContainer""" + sqlmeta = self.sqlmeta + for column in sqlmeta.columns: + if column.endswith('ID') and \ + isinstance(sqlmeta.columns[column], sqlobject.SOForeignKey): + yield sqlmeta.columns[column].foreignName + for join in sqlmeta.joins: + yield join.joinDef.name + + def items(self): + """See zope.app.container.interfaces.IReadContainer""" + for key in self.keys(): + yield (key, self[key]) + + def values(self): + """See zope.app.container.interfaces.IReadContainer""" + for key in self.keys(): + yield self[key] + + +class SQLOSTraversable(object): + """Traverses objects via item lookup and attribute""" + + implements(ITraversable) + + def __init__(self, subject): + self._subject = subject + + def traverse(self, name, furtherPath): + subject = self._subject + __traceback_info__ = (subject, name, furtherPath) + if hasattr(subject, '__getitem__'): + try: + return subject[name] + except KeyError: + pass + attr = getattr(subject, name, _marker) + if attr is not _marker: + return attr + raise TraversalError(subject, name) + + +class SelectResultsContainer: + """Adapter for SelectResults objects""" + + adapts(ISelectResults) + + implements(ISQLObjectJoinContainer) + + def __init__(self, context): + self.context = context + self.base_class = None + + def __len__(self): + return int(self.context.count()) + + def __getitem__(self, name): + if not self.base_class: + sqlmeta = removeSecurityProxy(self.__parent__.sqlmeta) + join = filter(lambda x: x.joinDef.name == self.__name__, sqlmeta.joins)[0] + self.base_class = join.otherClass + try: + result = list(self.context.filter( + self.base_class.q.id == self.base_class.sqlmeta.idType(name))) + if result: + return contained(result[0], name=unicode(name), parent=self) + except ValueError: + pass + raise KeyError, name + + def __iter__(self): + for j in self.context: + yield unicode(j.id) + + def keys(self): + return [unicode(j.id) for j in self.context] + + def items(self): + for j in self.context: + yield unicode(j.id), contained(j, name=unicode(j.id), parent=self) + + def values(self): + for j in self.context: + yield contained(j, name=unicode(j.id), parent=self) + + def __nonzero__(self): + return len(self) + + def get(self, name, default=None): + try: + return self[name] + except KeyError: + return default + + def __contains__(self, object): + return object in list(self.items()) + + def __delitem__(self, name): + """Delete the named object from the container. + + Raises a KeyError if the object is not found. + """ + obj = self[name] + obj.destroySelf() + + def __setitem__(self, name, content): + return name From kobold at codespeak.net Thu Jan 11 11:09:05 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Thu, 11 Jan 2007 11:09:05 +0100 (CET) Subject: [z3-checkins] r36456 - in z3/sqlos/trunk/src/sqlos: . testing Message-ID: <20070111100905.D7AED10088@code0.codespeak.net> Author: kobold Date: Thu Jan 11 11:08:50 2007 New Revision: 36456 Modified: z3/sqlos/trunk/src/sqlos/configure.zcml z3/sqlos/trunk/src/sqlos/ftesting.zcml z3/sqlos/trunk/src/sqlos/testing/sampleperson.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Splitted SQLOS and SQLOSContainer, so the users can choose which one they want to subclass. Modified: z3/sqlos/trunk/src/sqlos/configure.zcml ============================================================================== --- z3/sqlos/trunk/src/sqlos/configure.zcml (original) +++ z3/sqlos/trunk/src/sqlos/configure.zcml Thu Jan 11 11:08:50 2007 @@ -79,7 +79,7 @@ attribute="contents" /> - + - + >> from zope.interface.verify import verifyObject >>> verifyObject(ISQLObject, s) True - >>> verifyObject(IReadContainer, s) - True - And finally call tearDown and cleanup: @@ -66,10 +63,9 @@ """ - implements(ISQLObject, IReadContainer) + implements(ISQLObject) _connection = ConnectionDescriptor() - _containers = None class sqlmeta: lazyUpdate = True @@ -101,6 +97,36 @@ def setParent(self, parent): self.__parent__ = parent + +class SQLOSContainer(SQLOS): + """Subclass SQLOS to provide a container for subitems. The subitems + supported are ForeignKeys, SQLSingleJoin, SQLMultipleJoin and + SQLRelatedJoins. + + First, make a test data base: + + >>> from sqlos import testing + >>> testdb = testing.TestDB([SQLOSContainer]) + + Test the interface: + + >>> s = SQLOSContainer() + >>> from zope.interface.verify import verifyObject + >>> verifyObject(ISQLObject, s) + True + >>> verifyObject(IReadContainer, s) + True + + And finally call tearDown and cleanup: + + >>> testdb.tearDown() + + """ + + implements(IReadContainer) + + _containers = None + def __getitem__(self, name): """See zope.app.container.interfaces.IReadContainer""" if self._containers is None: From kobold at codespeak.net Thu Jan 11 11:27:10 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Thu, 11 Jan 2007 11:27:10 +0100 (CET) Subject: [z3-checkins] r36458 - z3/sqlos/trunk/src/sqlos/testing Message-ID: <20070111102710.6219A10080@code0.codespeak.net> Author: kobold Date: Thu Jan 11 11:27:09 2007 New Revision: 36458 Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py Log: ForeignKey are Attributes, not TextLine. Modified: z3/sqlos/trunk/src/sqlos/testing/sampleperson.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/testing/sampleperson.py (original) +++ z3/sqlos/trunk/src/sqlos/testing/sampleperson.py Thu Jan 11 11:27:09 2007 @@ -158,11 +158,7 @@ required=True, ) - owner = TextLine( - title=u'Owner username', - description=u'The username of the dog\'s owner', - required=True, - ) + owner = Attribute('Owner object') class SampleDog(SQLOSContainer): From kobold at codespeak.net Thu Jan 11 18:55:38 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Thu, 11 Jan 2007 18:55:38 +0100 (CET) Subject: [z3-checkins] r36520 - z3/sqlos/trunk/src/sqlos Message-ID: <20070111175538.7401A10063@code0.codespeak.net> Author: kobold Date: Thu Jan 11 18:55:35 2007 New Revision: 36520 Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Fixed objects returned by SingleJoin relationships. Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Thu Jan 11 18:55:35 2007 @@ -152,7 +152,9 @@ obj = getattr(self, name) if ISelectResults.providedBy(obj): obj = contained(ISQLObjectJoinContainer(obj), parent=self, name=name) - elif not ISQLObject.providedBy(obj): + elif ISQLObject.providedBy(obj): + obj = contained(obj, parent=self, name=name) + else: obj = None if obj is not None: From ianb at codespeak.net Fri Jan 12 00:58:59 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Fri, 12 Jan 2007 00:58:59 +0100 (CET) Subject: [z3-checkins] r36537 - z3/deliverance/DeliveranceVHoster Message-ID: <20070111235859.59B2710061@code0.codespeak.net> Author: ianb Date: Fri Jan 12 00:58:56 2007 New Revision: 36537 Added: z3/deliverance/DeliveranceVHoster/ Log: Renaming DeliveranceDemo project From ianb at codespeak.net Fri Jan 12 00:59:13 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Fri, 12 Jan 2007 00:59:13 +0100 (CET) Subject: [z3-checkins] r36538 - in z3/deliverance/DeliveranceVHoster: branches tags Message-ID: <20070111235913.A8DC61006F@code0.codespeak.net> Author: ianb Date: Fri Jan 12 00:59:11 2007 New Revision: 36538 Added: z3/deliverance/DeliveranceVHoster/branches/ z3/deliverance/DeliveranceVHoster/tags/ Log: Renaming DeliveranceDemo project From ianb at codespeak.net Fri Jan 12 00:59:29 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Fri, 12 Jan 2007 00:59:29 +0100 (CET) Subject: [z3-checkins] r36539 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070111235929.4063D10063@code0.codespeak.net> Author: ianb Date: Fri Jan 12 00:59:26 2007 New Revision: 36539 Added: z3/deliverance/DeliveranceVHoster/trunk/ - copied from r36538, z3/deliverance/DeliveranceDemo/trunk/ Log: Renaming DeliveranceDemo project From kobold at codespeak.net Fri Jan 12 19:17:34 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Fri, 12 Jan 2007 19:17:34 +0100 (CET) Subject: [z3-checkins] r36610 - z3/sqlos/trunk/src/sqlos Message-ID: <20070112181734.851EC10083@code0.codespeak.net> Author: kobold Date: Fri Jan 12 19:17:25 2007 New Revision: 36610 Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Do not break on MultipleJoin or RelatedJoin: we ignore them becase we do not support them. Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Fri Jan 12 19:17:25 2007 @@ -184,7 +184,10 @@ isinstance(sqlmeta.columns[column], sqlobject.SOForeignKey): yield sqlmeta.columns[column].foreignName for join in sqlmeta.joins: - yield join.joinDef.name + obj = getattr(self, join.joinDef.name) + if ISelectResults.providedBy(obj) or \ + ISQLObject.providedBy(obj): + yield join.joinDef.name def items(self): """See zope.app.container.interfaces.IReadContainer""" From kobold at codespeak.net Sat Jan 13 17:21:21 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sat, 13 Jan 2007 17:21:21 +0100 (CET) Subject: [z3-checkins] r36675 - in z3/sqlos/trunk/src/sqlos: . file Message-ID: <20070113162121.26B681008A@code0.codespeak.net> Author: kobold Date: Sat Jan 13 17:21:19 2007 New Revision: 36675 Modified: z3/sqlos/trunk/src/sqlos/file/__init__.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Fixes. Modified: z3/sqlos/trunk/src/sqlos/file/__init__.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/file/__init__.py (original) +++ z3/sqlos/trunk/src/sqlos/file/__init__.py Sat Jan 13 17:21:19 2007 @@ -1 +1,2 @@ -# import this +from fsutility import FileSystemFileStorage, FileSystemDataManager + Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Sat Jan 13 17:21:19 2007 @@ -19,7 +19,7 @@ from zope.app.container.interfaces import IReadContainer from zope.component import adapts from zope.security.proxy import removeSecurityProxy -from zope.traversing.interfaces import ITraversable +from zope.traversing.interfaces import ITraversable, TraversalError from zope.traversing.adapters import _marker from sqlos.connection import ConnectionDescriptor From regebro at codespeak.net Sun Jan 14 14:18:20 2007 From: regebro at codespeak.net (regebro at codespeak.net) Date: Sun, 14 Jan 2007 14:18:20 +0100 (CET) Subject: [z3-checkins] r36733 - z3/CMFonFive/trunk Message-ID: <20070114131820.A14991009D@code0.codespeak.net> Author: regebro Date: Sun Jan 14 14:18:19 2007 New Revision: 36733 Modified: z3/CMFonFive/trunk/CHANGES.txt z3/CMFonFive/trunk/README.txt z3/CMFonFive/trunk/version.txt Log: Preparing for 1.3.4 release. Modified: z3/CMFonFive/trunk/CHANGES.txt ============================================================================== --- z3/CMFonFive/trunk/CHANGES.txt (original) +++ z3/CMFonFive/trunk/CHANGES.txt Sun Jan 14 14:18:19 2007 @@ -1,6 +1,6 @@ CMFonFive Product Changelog - CMFonFive 1.3.4 (unreleased) + CMFonFive 1.3.4 (2007-01-14) - Browser menu items now have their filter expressions checked manually using CMF Expressions instead of testing item.available() which in Modified: z3/CMFonFive/trunk/README.txt ============================================================================== --- z3/CMFonFive/trunk/README.txt (original) +++ z3/CMFonFive/trunk/README.txt Sun Jan 14 14:18:19 2007 @@ -35,11 +35,11 @@ Download -------- -* CMFonFive 1.3.3 (2006-05-15) +* CMFonFive 1.3.4 (2007-01-14) For CMF 1.5.2 and later, with Zope 2.9.1 or later. - http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.3.3.tgz + http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.3.4.tgz Earlier versions @@ -48,6 +48,12 @@ These earlier versions are deprecated, but if you need CMFonFive for older versions of CMF or Zope, you can still download them here: +* CMFonFive 1.3.3 (2006-05-15) + + For CMF 1.5.2 and later, with Zope 2.9.1 or later. + + http://codespeak.net/z3/cmfonfive/release/CMFonFive-1.3.3.tgz + * CMFonFive 1.3.2 (2006-02-22) For CMF 1.5.2 and later, with Zope 2.9. Requires GenericSetup or CMF 1.6 @@ -119,3 +125,5 @@ * Tres Seaver (tseaver at zope.com) * Lennart Regebro (regebro at nuxeo.com) + +* Rocky Burt (rocky at serverzen.com) Modified: z3/CMFonFive/trunk/version.txt ============================================================================== --- z3/CMFonFive/trunk/version.txt (original) +++ z3/CMFonFive/trunk/version.txt Sun Jan 14 14:18:19 2007 @@ -1 +1 @@ -CMFonFive-1.3.4 (svn/unreleased) +CMFonFive-1.3.4 From regebro at codespeak.net Sun Jan 14 14:21:37 2007 From: regebro at codespeak.net (regebro at codespeak.net) Date: Sun, 14 Jan 2007 14:21:37 +0100 (CET) Subject: [z3-checkins] r36734 - z3/CMFonFive/tag/CMFonFive-1.3.4 Message-ID: <20070114132137.931CE1009D@code0.codespeak.net> Author: regebro Date: Sun Jan 14 14:21:37 2007 New Revision: 36734 Added: z3/CMFonFive/tag/CMFonFive-1.3.4/ - copied from r36733, z3/CMFonFive/trunk/ Log: Releasing 1.3.4 From regebro at codespeak.net Sun Jan 14 14:29:57 2007 From: regebro at codespeak.net (regebro at codespeak.net) Date: Sun, 14 Jan 2007 14:29:57 +0100 (CET) Subject: [z3-checkins] r36735 - z3/www/trunk Message-ID: <20070114132957.E4B4D1009D@code0.codespeak.net> Author: regebro Date: Sun Jan 14 14:29:56 2007 New Revision: 36735 Modified: z3/www/trunk/mkwebsite.py Log: Added CMFonFive 1.3.4. Modified: z3/www/trunk/mkwebsite.py ============================================================================== --- z3/www/trunk/mkwebsite.py (original) +++ z3/www/trunk/mkwebsite.py Sun Jan 14 14:29:56 2007 @@ -212,6 +212,7 @@ Z3ReleaseResource('CMFonFive', 'http://codespeak.net/svn/z3/CMFonFive/tag/CMFonFive-1.1.1'), Z3ReleaseResource('CMFonFive', 'http://codespeak.net/svn/z3/CMFonFive/tag/CMFonFive-1.2.1'), Z3ReleaseResource('CMFonFive', 'http://codespeak.net/svn/z3/CMFonFive/tag/CMFonFive-1.3.3'), + Z3ReleaseResource('CMFonFive', 'http://codespeak.net/svn/z3/CMFonFive/tag/CMFonFive-1.3.4'), ], project.getName()) From philikon at codespeak.net Tue Jan 16 19:09:53 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 16 Jan 2007 19:09:53 +0100 (CET) Subject: [z3-checkins] r36841 - z3/FiveException/tags Message-ID: <20070116180953.95104100D0@code0.codespeak.net> Author: philikon Date: Tue Jan 16 19:09:52 2007 New Revision: 36841 Added: z3/FiveException/tags/ Log: Tags dir From philikon at codespeak.net Tue Jan 16 19:10:02 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 16 Jan 2007 19:10:02 +0100 (CET) Subject: [z3-checkins] r36842 - z3/FiveException/tags/Zope-2.8 Message-ID: <20070116181002.6BCCC100D0@code0.codespeak.net> Author: philikon Date: Tue Jan 16 19:10:00 2007 New Revision: 36842 Added: z3/FiveException/tags/Zope-2.8/ - copied from r36841, z3/FiveException/trunk/ Log: Tag Zope 2.8-compatible version From philikon at codespeak.net Tue Jan 16 19:13:17 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 16 Jan 2007 19:13:17 +0100 (CET) Subject: [z3-checkins] r36843 - z3/FiveException/trunk Message-ID: <20070116181317.D5463100BA@code0.codespeak.net> Author: philikon Date: Tue Jan 16 19:13:16 2007 New Revision: 36843 Modified: z3/FiveException/trunk/monkey.py Log: Fix up for Zope 2.9 Modified: z3/FiveException/trunk/monkey.py ============================================================================== --- z3/FiveException/trunk/monkey.py (original) +++ z3/FiveException/trunk/monkey.py Tue Jan 16 19:13:16 2007 @@ -6,8 +6,8 @@ from ZODB.POSException import ConflictError from zLOG import LOG, INFO, BLATHER import ZPublisher -from Zope.App.startup import RequestContainer, app -from zope.component import getView, ComponentLookupError +from Zope2.App.startup import RequestContainer, app +from zope.component import getMultiAdapter, ComponentLookupError from Products.FiveException.interfaces import IZope2HandledException @@ -67,7 +67,7 @@ raise t, v, traceback try: - view = getView(v, 'index.html', REQUEST) + view = getMultiAdapter((v, REQUEST), 'index.html') except ComponentLookupError: raise t, v, traceback # XXX utter hack here to fool Five into working. Five should From ianb at codespeak.net Wed Jan 17 15:54:05 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 17 Jan 2007 15:54:05 +0100 (CET) Subject: [z3-checkins] r36874 - in z3/deliverance/DeliveranceVHoster/trunk: . ddemo dvhoster Message-ID: <20070117145405.C688D10070@code0.codespeak.net> Author: ianb Date: Wed Jan 17 15:53:55 2007 New Revision: 36874 Added: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/ - copied from r36859, z3/deliverance/DeliveranceVHoster/trunk/ddemo/ Removed: z3/deliverance/DeliveranceVHoster/trunk/ddemo/ Modified: z3/deliverance/DeliveranceVHoster/trunk/README.txt z3/deliverance/DeliveranceVHoster/trunk/development.ini z3/deliverance/DeliveranceVHoster/trunk/setup.cfg z3/deliverance/DeliveranceVHoster/trunk/setup.py Log: continuing renaming Modified: z3/deliverance/DeliveranceVHoster/trunk/README.txt ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/README.txt (original) +++ z3/deliverance/DeliveranceVHoster/trunk/README.txt Wed Jan 17 15:53:55 2007 @@ -1,16 +1,16 @@ -This file is for you to describe the DeliveranceDemo application. Typically +This file is for you to describe the DeliveranceVHoster application. Typically you would include information such as the information below: Installation and Setup ====================== -Install ``DeliveranceDemo`` using easy_install:: +Install ``DeliveranceVHoster`` using easy_install:: - easy_install DeliveranceDemo + easy_install DeliveranceVHoster Make a config file as follows:: - paster make-config DeliveranceDemo config.ini + paster make-config DeliveranceVHoster config.ini Tweak the config file as appropriate and then setup the application:: Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/development.ini (original) +++ z3/deliverance/DeliveranceVHoster/trunk/development.ini Wed Jan 17 15:53:55 2007 @@ -5,7 +5,7 @@ # [DEFAULT] debug = true -email_to = you at yourdomain.com +email_to = ianb at openplans.org smtp_server = localhost error_email_from = paste at localhost @@ -15,32 +15,14 @@ port = 5000 [app:main] -use = egg:DeliveranceDemo -full_stack = True -cache_dir = %(here)s/cache_data +use = egg:DeliveranceVHoster data_dir = %(here)s/data -session_key = ddemo -session_secret = somesecret debug_headers = true # To view bodies: #debug_bodies = true # To disable the rewriting of links: #rewrite_links = false - -# If you'd like to fine-tune the individual locations of the cache data dirs -# for Myghty, the Cache data, or the Session saves, un-comment the desired -# settings here: -#myghty_data_dir = %(here)s/data/templates -#cache_data_dir = %(here)s/data/cache -#session_data_dir = %(here)s/data/sessions - -# Specify the database for SQLObject to use via pylons.database.PackageHub. -# %(here) may include a ':' character on Windows environments; this can -# invalidate the URI when specifying a SQLite db via path name. Refer to the -# SQLObject documentation for a special syntax to preserve the URI. -#sqlobject.dburi = sqlite:%(here)s/somedb.db - # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* # Debug mode will enable the interactive debugging tool, allowing ANYONE to # execute malicious code after an exception is raised. Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.cfg ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.cfg (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.cfg Wed Jan 17 15:53:55 2007 @@ -9,20 +9,17 @@ theme = pythonpaste.org # Add extra doc files here with spaces between them -docs = ddemo/docs/index.txt +docs = docs/index.txt # Doc Settings -doc_base = ddemo/docs/ -dest = ddemo/docs/html +doc_base = docs/ +dest = docs/html # Add extra modules here separated with commas modules = ddemo title = Ddemo organization = Pylons -# Optionally add extra links -#organization_url = http://pylons.groovie.org/ -#trac_url=http://pylons.groovie.org/ settings = no_about=true # Optionally add extra settings @@ -30,5 +27,5 @@ # link2=/download/ Download [publish] -doc-dir=ddemo/docs/html +doc-dir=docs/html make-dirs=1 Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.py Wed Jan 17 15:53:55 2007 @@ -1,14 +1,14 @@ from setuptools import setup, find_packages setup( - name='DeliveranceDemo', + name='DeliveranceVHoster', version="0.1", - description="A demo setup of Deliverance, self-configuring", + description="A virtual-hosting proxy for Deliverance", #author="", #author_email="", #url="", install_requires=[ - 'Paste==dev,>1.0', # There's a bug fix we need + 'Paste==dev,>1.1.1', # There's a bug fix we need "Pylons>=0.9.3", 'Deliverance', 'WSGIFilter', @@ -17,7 +17,6 @@ packages=find_packages(), include_package_data=True, test_suite = 'nose.collector', - package_data={'ddemo': ['i18n/*/LC_MESSAGES/*.mo']}, entry_points=""" [paste.app_factory] main=ddemo:make_app From ianb at codespeak.net Wed Jan 17 16:00:54 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 17 Jan 2007 16:00:54 +0100 (CET) Subject: [z3-checkins] r36875 - in z3/deliverance/DeliveranceVHoster/trunk: . DeliveranceDemo.egg-info DeliveranceVHoster.egg-info docs Message-ID: <20070117150054.52D0110075@code0.codespeak.net> Author: ianb Date: Wed Jan 17 16:00:51 2007 New Revision: 36875 Added: z3/deliverance/DeliveranceVHoster/trunk/DeliveranceVHoster.egg-info/ (props changed) Removed: z3/deliverance/DeliveranceVHoster/trunk/DeliveranceDemo.egg-info/ Modified: z3/deliverance/DeliveranceVHoster/trunk/docs/find-links.html z3/deliverance/DeliveranceVHoster/trunk/setup.py Log: more renaming Modified: z3/deliverance/DeliveranceVHoster/trunk/docs/find-links.html ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/docs/find-links.html (original) +++ z3/deliverance/DeliveranceVHoster/trunk/docs/find-links.html Wed Jan 17 16:00:51 2007 @@ -7,8 +7,6 @@ Link to Deliverance svn checkout -Pylons -link :( supervisor Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.py Wed Jan 17 16:00:51 2007 @@ -9,17 +9,20 @@ #url="", install_requires=[ 'Paste==dev,>1.1.1', # There's a bug fix we need - "Pylons>=0.9.3", 'Deliverance', 'WSGIFilter', 'HTTPEncode', + 'OHM', ], + dependency_links=[ + 'http://codespeak.net/svn/z3/deliverance/trunk#egg=Deliverance-dev', + ], packages=find_packages(), include_package_data=True, test_suite = 'nose.collector', entry_points=""" [paste.app_factory] - main=ddemo:make_app + main=dvhoster:make_app [paste.app_install] main=paste.script.appinstall:Installer """, From ianb at codespeak.net Wed Jan 17 16:04:27 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 17 Jan 2007 16:04:27 +0100 (CET) Subject: [z3-checkins] r36876 - in z3/deliverance/DeliveranceVHoster/trunk/dvhoster: . controllers tests tests/functional Message-ID: <20070117150427.40FB310077@code0.codespeak.net> Author: ianb Date: Wed Jan 17 16:04:19 2007 New Revision: 36876 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/__init__.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/__init__.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/index.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/remote_getter.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/__init__.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_index.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_remote_getter.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py Log: Python code renaming Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/__init__.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/__init__.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/__init__.py Wed Jan 17 16:04:19 2007 @@ -1,7 +1,7 @@ """ -ddemo +dvhoster -This file loads the finished app from ddemo.config.middleware. +This file loads the finished app from dvhoster.config.middleware. """ @@ -12,4 +12,4 @@ current_environ = registry.StackedObjectProxy( name='environ') -from ddemo.wsgiapp import make_app +from dvhoster.wsgiapp import make_app Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/__init__.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/__init__.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/__init__.py Wed Jan 17 16:04:19 2007 @@ -4,14 +4,14 @@ from pylons.templating import render, render_response from pylons.helpers import abort, redirect_to, etag_cache from paste.deploy import CONFIG -import ddemo.helpers as h +import dvhoster.helpers as h class BaseController(WSGIController): def __call__(self, environ, start_response): # Insert any code to be run per request here. The Routes match # is under environ['pylons.routes_dict'] should you want to check # the action or route vars here - di = c.domain_info = environ['ddemo.domain_info'] + di = c.domain_info = environ['dvhoster.domain_info'] if not di.initialized: di.initialize() return WSGIController.__call__(self, environ, start_response) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/index.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/index.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/index.py Wed Jan 17 16:04:19 2007 @@ -1,4 +1,4 @@ -from ddemo.controllers import * +from dvhoster.controllers import * import os import re import urlparse @@ -57,7 +57,7 @@ def update(self): params = request.params conf = CONFIG['app_conf'] - base = request.environ['ddemo.base_url'] + base = request.environ['dvhoster.base_url'] di = c.domain_info base_parts = urlparse.urlsplit(base) scheme, netloc, path, query, fragment = base_parts Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/remote_getter.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/remote_getter.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/remote_getter.py Wed Jan 17 16:04:19 2007 @@ -1,4 +1,4 @@ -from ddemo.controllers import * +from dvhoster.controllers import * from httpencode.lxmlformat import xml as lxml_format from httpencode.json import json as json_format from httpencode import BadRequestError Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py Wed Jan 17 16:04:19 2007 @@ -1,7 +1,7 @@ from deliverance.interpreter import Renderer as PyRenderer from lxml import etree from paste.request import construct_url -from ddemo import current_environ +from dvhoster import current_environ class Renderer(PyRenderer): @@ -14,9 +14,9 @@ error.text = 'Error with no message (%s)' % message error_container = etree.Element('div') error_container.attrib['style'] = self.error_style - if not current_environ.get('ddemo.has_errors'): - current_environ['ddemo.has_errors'] = True - domain_info = current_environ['ddemo.domain_info'] + if not current_environ.get('dvhoster.has_errors'): + current_environ['dvhoster.has_errors'] = True + domain_info = current_environ['dvhoster.domain_info'] remote = domain_info.remote remote += current_environ.get('PATH_INFO', '') if current_environ.get('QUERY_STRING'): Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Wed Jan 17 16:04:19 2007 @@ -8,9 +8,9 @@ from wsgifilter import proxyapp from wsgifilter import relocateresponse from deliverance.wsgimiddleware import DeliveranceMiddleware -from ddemo.dataprovider import DataProvider -from ddemo import current_environ -from ddemo.debuginterp import Renderer +from dvhoster.dataprovider import DataProvider +from dvhoster import current_environ +from dvhoster.debuginterp import Renderer def norm_path(urlpath): if not urlpath: @@ -34,8 +34,8 @@ if ':' in domain: domain = domain.split(':', 1)[0] domain_info = self.provider.domain(domain) - environ['ddemo.domain_info'] = domain_info - environ['ddemo.base_url'] = construct_url( + environ['dvhoster.domain_info'] = domain_info + environ['dvhoster.base_url'] = construct_url( environ, with_query_string=False, path_info='') path_info = norm_path(environ.get('PATH_INFO', '')) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/__init__.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/__init__.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/__init__.py Wed Jan 17 16:04:19 2007 @@ -16,7 +16,7 @@ from paste.deploy import loadapp import paste.fixture -from ddemo.config.routing import * +from dvhoster.config.routing import * from routes import request_config, url_for class TestController(TestCase): Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_index.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_index.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_index.py Wed Jan 17 16:04:19 2007 @@ -1,6 +1,6 @@ -from ddemo.tests import * +from dvhoster.tests import * class TestIndexController(TestController): def test_index(self): response = self.app.get(url_for(controller='index')) - # Test response... \ No newline at end of file + # Test response... Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_remote_getter.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_remote_getter.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/functional/test_remote_getter.py Wed Jan 17 16:04:19 2007 @@ -1,6 +1,6 @@ -from ddemo.tests import * +from dvhoster.tests import * class TestRemoteGetterController(TestController): def test_index(self): response = self.app.get(url_for(controller='remote_getter')) - # Test response... \ No newline at end of file + # Test response... Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py Wed Jan 17 16:04:19 2007 @@ -14,10 +14,10 @@ from pylons.error import error_template from pylons.middleware import ErrorHandler, ErrorDocuments, StaticJavascripts, error_mapper -import ddemo.helpers -from ddemo.routing import make_map +import dvhoster.helpers +from dvhoster.routing import make_map -from ddemo.dispatcher import DeliveranceDispatcher +from dvhoster.dispatcher import DeliveranceDispatcher def load_environment(global_conf={}, app_conf={}): map = make_map(global_conf, app_conf) @@ -38,10 +38,10 @@ """Create a WSGI application and return it""" # Load our Pylons configuration defaults config = load_environment(global_conf, app_conf) - config.init_app(global_conf, app_conf, package='ddemo') + config.init_app(global_conf, app_conf, package='dvhoster') # Load our default Pylons WSGI app and make g available - app = pylons.wsgiapp.PylonsApp(config, helpers=ddemo.helpers, g=Globals) + app = pylons.wsgiapp.PylonsApp(config, helpers=dvhoster.helpers, g=Globals) g = app.globals app = ConfigMiddleware(app, {'app_conf':app_conf, 'global_conf':global_conf}) From ianb at codespeak.net Wed Jan 17 16:14:23 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 17 Jan 2007 16:14:23 +0100 (CET) Subject: [z3-checkins] r36877 - in z3/deliverance/DeliveranceVHoster/trunk: . dvhoster dvhoster/controllers dvhoster/public dvhoster/templates dvhoster/tests Message-ID: <20070117151423.CC32110077@code0.codespeak.net> Author: ianb Date: Wed Jan 17 16:14:20 2007 New Revision: 36877 Removed: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/controllers/ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/helpers.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/public/ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/routing.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/templates/ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/tests/ Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py z3/deliverance/DeliveranceVHoster/trunk/setup.cfg z3/deliverance/DeliveranceVHoster/trunk/supervisor.conf Log: Got rid of all the Pylons stuff Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Wed Jan 17 16:14:20 2007 @@ -21,8 +21,7 @@ class DeliveranceDispatcher(object): - def __init__(self, pylons_app, app_conf): - self.pylons_app = pylons_app + def __init__(self, app_conf): data_dir = app_conf['data_dir'] self.provider = DataProvider(data_dir) self.rewrite_links = asbool(app_conf.get('rewrite_links', True)) @@ -39,9 +38,6 @@ environ, with_query_string=False, path_info='') path_info = norm_path(environ.get('PATH_INFO', '')) - if path_info.startswith('/_deliverance'): - path_info_pop(environ) - return self.pylons_app(environ, start_response) if (not domain_info.initialized or not domain_info.remote or not domain_info.theme_uri): Deleted: /z3/deliverance/DeliveranceVHoster/trunk/dvhoster/helpers.py ============================================================================== --- /z3/deliverance/DeliveranceVHoster/trunk/dvhoster/helpers.py Wed Jan 17 16:14:20 2007 +++ (empty file) @@ -1,9 +0,0 @@ -""" -Helper functions - -All names available in this module will be available under the Pylons h object. -""" -from webhelpers import * -from webhelpers.util import * -from pylons.helpers import * - Deleted: /z3/deliverance/DeliveranceVHoster/trunk/dvhoster/routing.py ============================================================================== --- /z3/deliverance/DeliveranceVHoster/trunk/dvhoster/routing.py Wed Jan 17 16:14:20 2007 +++ (empty file) @@ -1,10 +0,0 @@ -"""Setup your Routes options here""" -import os -from routes import Mapper - -def make_map(global_conf={}, app_conf={}): - root_path = os.path.dirname(os.path.abspath(__file__)) - map = Mapper(directory=os.path.join(root_path, 'controllers')) - map.connect(':controller/:action/:id') - map.connect('', controller='index') - return map Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py Wed Jan 17 16:14:20 2007 @@ -1,68 +1,26 @@ -import os - -from paste import httpexceptions from paste.cascade import Cascade from paste.urlparser import StaticURLParser -from paste.registry import RegistryManager -from paste.deploy.config import ConfigMiddleware +#from paste.deploy.config import ConfigMiddleware from paste.deploy.converters import asbool from paste.recursive import RecursiveMiddleware from wsgifilter import proxyapp - -import pylons.wsgiapp -import pylons.config -from pylons.error import error_template -from pylons.middleware import ErrorHandler, ErrorDocuments, StaticJavascripts, error_mapper - -import dvhoster.helpers -from dvhoster.routing import make_map +from paste.exceptions.errormiddleware import ErrorMiddleware from dvhoster.dispatcher import DeliveranceDispatcher -def load_environment(global_conf={}, app_conf={}): - map = make_map(global_conf, app_conf) - root_path = os.path.dirname(os.path.abspath(__file__)) - paths = {'root_path': root_path, - 'controllers': os.path.join(root_path, 'controllers'), - 'templates': [os.path.join(root_path, 'templates')], - 'static_files': os.path.join(root_path, 'public') - } - myghty = dict(log_errors=True) - return pylons.config.Config(myghty, map, paths) - -class Globals(object): - def __init__(self, global_conf, app_conf, **extra): - pass - -def make_app(global_conf, full_stack=True, **app_conf): +def make_app(global_conf, **app_conf): """Create a WSGI application and return it""" - # Load our Pylons configuration defaults - config = load_environment(global_conf, app_conf) - config.init_app(global_conf, app_conf, package='dvhoster') - - # Load our default Pylons WSGI app and make g available - app = pylons.wsgiapp.PylonsApp(config, helpers=dvhoster.helpers, g=Globals) - g = app.globals - app = ConfigMiddleware(app, {'app_conf':app_conf, - 'global_conf':global_conf}) - - # <-- YOUR MIDDLEWARE HERE - - # Build the rest of the stack, see a full template for more details - if asbool(full_stack): - app = httpexceptions.make_middleware(app, global_conf) - app = ErrorHandler(app, global_conf, error_template=error_template, **config.errorware) - - static_app = StaticURLParser(config.paths['static_files']) - javascripts_app = StaticJavascripts() - app = Cascade([static_app, javascripts_app, app]) - app = DeliveranceDispatcher(app, app_conf) + app = DeliveranceDispatcher(app_conf) app = RecursiveMiddleware(app) + debug = app_conf['debug'] = asbool(app_conf.get('debug', global_conf.get('debug'))) if asbool(app_conf.get('debug_headers')): app = proxyapp.DebugHeaders(app, show_body=asbool(app_conf.get('debug_bodies'))) - app = RegistryManager(app) - if (asbool(full_stack) - and asbool(app_conf.get('debug', global_conf.get('debug')))): + if debug: from paste.evalexception import EvalException app = EvalException(app) + else: + folded_conf = global_conf.copy() + folded_conf.update(app_conf) + app = ErrorMiddleware( + app, global_conf=folded_conf) return app Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.cfg ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.cfg (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.cfg Wed Jan 17 16:14:20 2007 @@ -16,8 +16,8 @@ dest = docs/html # Add extra modules here separated with commas -modules = ddemo -title = Ddemo +modules = dvhoster +title = DeliveranceVHoster organization = Pylons settings = no_about=true Modified: z3/deliverance/DeliveranceVHoster/trunk/supervisor.conf ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/supervisor.conf (original) +++ z3/deliverance/DeliveranceVHoster/trunk/supervisor.conf Wed Jan 17 16:14:20 2007 @@ -31,12 +31,12 @@ ;password=123 ; should be same as http_password if set ;prompt=mysupervisor ; cmd line prompt (default "supervisor") -[program:ddemo] +[program:dvhoster] ;command=sh -c "exec paster serve development.ini" command=paster serve development.ini autostart=true autorestart=true -logfile=./tmp/ddemo.log +logfile=./tmp/dvhoster.log ;logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) ;logfile_backups=10 ; # of logfile backups (default 10) From ltucker at codespeak.net Wed Jan 17 16:51:31 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 17 Jan 2007 16:51:31 +0100 (CET) Subject: [z3-checkins] r36879 - z3/deliverance/DeliveranceDemo/trunk/ddemo Message-ID: <20070117155131.2F35F10078@code0.codespeak.net> Author: ltucker Date: Wed Jan 17 16:51:29 2007 New Revision: 36879 Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py Log: clean up attributes from filesystem Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py Wed Jan 17 16:51:29 2007 @@ -59,8 +59,9 @@ class file_property(object): - def __init__(self, basename): + def __init__(self, basename, strip=False): self.basename = basename + self.strip = string def filename(self, obj): return os.path.join(obj.dir, self.basename) @@ -73,6 +74,8 @@ f = open(self.filename(obj), 'rb') c = f.read() f.close() + if self.strip: + c = c.strip() return c def __set__(self, obj, value): @@ -126,10 +129,10 @@ def static_dir(self): return os.path.join(self.dir, 'static') - remote = file_property('remote.txt') - theme_uri = file_property('theme_uri.txt') - theme_id = file_property('theme_id.txt') - rule_ids = file_property('rule_ids.txt') + remote = file_property('remote.txt', strip=True) + theme_uri = file_property('theme_uri.txt', strip=True) + theme_id = file_property('theme_id.txt', strip=True) + rule_ids = file_property('rule_ids.txt', strip=True) def set_rule_file(self, filename, content): filename = os.path.join(self.rule_dir, filename) From ltucker at codespeak.net Wed Jan 17 16:51:45 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 17 Jan 2007 16:51:45 +0100 (CET) Subject: [z3-checkins] r36880 - z3/deliverance/DeliveranceDemo/trunk/ddemo Message-ID: <20070117155145.376C910079@code0.codespeak.net> Author: ltucker Date: Wed Jan 17 16:51:43 2007 New Revision: 36880 Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dispatcher.py Log: Add logging of remote URLs Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/ddemo/dispatcher.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/ddemo/dispatcher.py Wed Jan 17 16:51:43 2007 @@ -11,6 +11,9 @@ from ddemo.dataprovider import DataProvider from ddemo import current_environ from ddemo.debuginterp import Renderer +import logging +logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(message)s') def norm_path(urlpath): if not urlpath: @@ -63,9 +66,11 @@ # Explicit override of a file app = FileApp(static_fn) return app(environ, start_response) + import logging app = proxyapp.ForcedProxy( remote=domain_info.remote, - force_host=True) + force_host=True, + logger=logging.getLogger('')) if self.rewrite_links: app = relocateresponse.RelocateMiddleware( app, old_href=domain_info.remote) From kobold at codespeak.net Wed Jan 17 18:14:20 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Wed, 17 Jan 2007 18:14:20 +0100 (CET) Subject: [z3-checkins] r36884 - z3/sqlos/trunk/src/sqlos Message-ID: <20070117171420.6268810070@code0.codespeak.net> Author: kobold Date: Wed Jan 17 18:14:19 2007 New Revision: 36884 Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Added a new test. Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Wed Jan 17 18:14:19 2007 @@ -121,6 +121,46 @@ >>> testdb.tearDown() + Test a join with None values: + + >>> from sqlobject import * + >>> from zope.interface import Attribute + >>> from sqlos.interfaces import ISQLSchema + + >>> class IPerson(ISQLSchema): + ... fullname = Attribute(u'Fullname') + ... company = Attribute(u'Company') + + >>> class Person(SQLOSContainer): + ... fullname = UnicodeCol(length=100) + ... company = SingleJoin('Company') + + >>> class ICompany(ISQLSchema): + ... name = Attribute(u'Name') + ... person = Attribute(u'Person') + + >>> class Company(SQLOSContainer): + ... name = UnicodeCol(length=100) + ... person = ForeignKey('Person') + + >>> testdb = testing.TestDB([Person, Company]) + >>> person = Person(fullname='John Doe') + >>> person.company is None + True + >>> list(person.keys()) + [] + + >>> company = Company(name='Company Ltd.', person=person) + >>> list(person.keys()) + ['company'] + + >>> person['company'].name + u'Company Ltd.' + + And finally call tearDown and cleanup: + + >>> testdb.tearDown() + """ implements(IReadContainer) From kobold at codespeak.net Wed Jan 17 18:44:03 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Wed, 17 Jan 2007 18:44:03 +0100 (CET) Subject: [z3-checkins] r36885 - z3/sqlos/branch/kobold-sqlos Message-ID: <20070117174403.1EA7810070@code0.codespeak.net> Author: kobold Date: Wed Jan 17 18:44:02 2007 New Revision: 36885 Removed: z3/sqlos/branch/kobold-sqlos/ Log: Removed kobold-sqlos branch. From kobold at codespeak.net Sun Jan 21 13:06:42 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sun, 21 Jan 2007 13:06:42 +0100 (CET) Subject: [z3-checkins] r37087 - in z3/sqlos/trunk/src/sqlos: . interfaces Message-ID: <20070121120642.E657B10077@code0.codespeak.net> Author: kobold Date: Sun Jan 21 10:50:30 2007 New Revision: 37087 Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: SQLObjectJoinContainer now implements ISelectResults, too; added unit test. Modified: z3/sqlos/trunk/src/sqlos/interfaces/container.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/interfaces/container.py (original) +++ z3/sqlos/trunk/src/sqlos/interfaces/container.py Sun Jan 21 10:50:30 2007 @@ -20,6 +20,8 @@ from zope.app.container.interfaces import IContainerNamesContainer, IReadContainer, \ IContainer, IContained +from sqlos.interfaces import ISelectResults + class ISQLObjectContainer(IContainerNamesContainer, IAttributeAnnotatable): """A SQLObject container""" @@ -43,7 +45,7 @@ """A mono-type SQLObject container""" -class ISQLObjectJoinContainer(IContainerNamesContainer, IEnumerableMapping, IContained): +class ISQLObjectJoinContainer(IContainerNamesContainer, IEnumerableMapping, IContained, ISelectResults): """A SQLObject container for joins""" Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Sun Jan 21 10:50:30 2007 @@ -127,18 +127,10 @@ >>> from zope.interface import Attribute >>> from sqlos.interfaces import ISQLSchema - >>> class IPerson(ISQLSchema): - ... fullname = Attribute(u'Fullname') - ... company = Attribute(u'Company') - >>> class Person(SQLOSContainer): ... fullname = UnicodeCol(length=100) ... company = SingleJoin('Company') - >>> class ICompany(ISQLSchema): - ... name = Attribute(u'Name') - ... person = Attribute(u'Person') - >>> class Company(SQLOSContainer): ... name = UnicodeCol(length=100) ... person = ForeignKey('Person') @@ -263,7 +255,50 @@ class SelectResultsContainer: - """Adapter for SelectResults objects""" + """Adapter for SelectResults objects + + Test the join container: + + >>> from sqlobject import * + >>> from zope.interface import Attribute + >>> from sqlos.interfaces import ISQLSchema + + >>> class User(SQLOSContainer): + ... name = UnicodeCol(length=100) + ... addresses = SQLMultipleJoin('Address', joinColumn='user_id') + + >>> class Address(SQLOSContainer): + ... address = UnicodeCol(length=100) + ... user = ForeignKey('User', cascade=True) + + >>> from sqlos import testing + >>> testdb = testing.TestDB([User, Address]) + + >>> user = User(name='John Doe') + >>> user.addresses is None + False + + >>> address = Address(address='Test Address', user=user) + >>> user.addresses.count() + 1 + + >>> from sqlos.container import contained + >>> container = contained(SelectResultsContainer(user.addresses), + ... name='test', parent=user) + + >>> from zope.interface.verify import verifyObject + >>> verifyObject(ISQLObjectJoinContainer, container) + True + >>> verifyObject(ISelectResults, container) + True + >>> container.count() + 1 + + And finally call tearDown and cleanup: + + >>> testdb.tearDown() + + """ adapts(ISelectResults) @@ -327,3 +362,84 @@ def __setitem__(self, name, content): return name + + def orderBy(self, orderBy): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.orderBy(orderBy), + name=self.__name__, parent=self.__parent__) + + def newClause(self, newClause): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.newClause(newClause), + name=self.__name__, parent=self.__parent__) + + def max(self, attribute): + """See sqlos.interfaces.ISelectResults""" + return self.context.max(attribute) + + def min(self, attribute): + """See sqlos.interfaces.ISelectResults""" + return self.context.min(attribute) + + def sum(self, attribute): + """See sqlos.interfaces.ISelectResults""" + return self.context.sum(attribute) + + def avg(self, attribute): + """See sqlos.interfaces.ISelectResults""" + return self.context.avg(attribute) + + def reversed(self): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.reversed(), + name=self.__name__, parent=self.__parent__) + + def accumulate(self, *expressions): + """See sqlos.interfaces.ISelectResults""" + return self.context.accumulate(*expressions) + + def count(self): + """See sqlos.interfaces.ISelectResults""" + return self.context.count() + + def lazyIter(self): + """See sqlos.interfaces.ISelectResults""" + return self.context.lazyIter() + + def accumulateOne(self, func_name, attribute): + """See sqlos.interfaces.ISelectResults""" + return self.context.accumulateOne(func_name, attribute) + + def accumulateMany(self, *attributes): + """See sqlos.interfaces.ISelectResults""" + return self.context.accumulateMany(*attributes) + + def distinct(self): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.distinct(), + name=self.__name__, parent=self.__parent__) + + def filter(self, filter_clause): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.filter(filter_clause), + name=self.__name__, parent=self.__parent__) + + def connection(self, conn): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.connection(connection=con), + name=self.__name__, parent=self.__parent__) + + def limit(self, limit): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.limit(limit), + name=self.__name__, parent=self.__parent__) + + def lazyColumns(self, value): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.lazyColumns(value), + name=self.__name__, parent=self.__parent__) + + def clone(self, **newOpts): + """See sqlos.interfaces.ISelectResults""" + return contained(self.context.clone(**newOpts), + name=self.__name__, parent=self.__parent__) From kobold at codespeak.net Sun Jan 21 13:06:49 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sun, 21 Jan 2007 13:06:49 +0100 (CET) Subject: [z3-checkins] r37089 - z3/sqlos/trunk/src/sqlos Message-ID: <20070121120649.93FF310078@code0.codespeak.net> Author: kobold Date: Sun Jan 21 11:11:13 2007 New Revision: 37089 Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: Fixed proxy issues. Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Sun Jan 21 11:11:13 2007 @@ -365,12 +365,12 @@ def orderBy(self, orderBy): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.orderBy(orderBy), + return contained(ISQLObjectJoinContainer(self.context.orderBy(orderBy)), name=self.__name__, parent=self.__parent__) def newClause(self, newClause): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.newClause(newClause), + return contained(ISQLObjectJoinContainer(self.context.newClause(newClause)), name=self.__name__, parent=self.__parent__) def max(self, attribute): @@ -391,7 +391,7 @@ def reversed(self): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.reversed(), + return contained(ISQLObjectJoinContainer(self.context.reversed()), name=self.__name__, parent=self.__parent__) def accumulate(self, *expressions): @@ -416,30 +416,30 @@ def distinct(self): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.distinct(), + return contained(ISQLObjectJoinContainer(self.context.distinct()), name=self.__name__, parent=self.__parent__) def filter(self, filter_clause): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.filter(filter_clause), + return contained(ISQLObjectJoinContainer(self.context.filter(filter_clause)), name=self.__name__, parent=self.__parent__) def connection(self, conn): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.connection(connection=con), + return contained(ISQLObjectJoinContainer(self.context.connection(connection=con)), name=self.__name__, parent=self.__parent__) def limit(self, limit): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.limit(limit), + return contained(ISQLObjectJoinContainer(self.context.limit(limit)), name=self.__name__, parent=self.__parent__) def lazyColumns(self, value): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.lazyColumns(value), + return contained(ISQLObjectJoinContainer(self.context.lazyColumns(value)), name=self.__name__, parent=self.__parent__) def clone(self, **newOpts): """See sqlos.interfaces.ISelectResults""" - return contained(self.context.clone(**newOpts), + return contained(ISQLObjectJoinContainer(self.context.clone(**newOpts)), name=self.__name__, parent=self.__parent__) From ianb at codespeak.net Tue Jan 23 17:29:20 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Tue, 23 Jan 2007 17:29:20 +0100 (CET) Subject: [z3-checkins] r37213 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070123162920.E01AB1007D@code0.codespeak.net> Author: ianb Date: Tue Jan 23 17:29:19 2007 New Revision: 37213 Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py Log: Added wsgi_intercept requirement Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.py Tue Jan 23 17:29:19 2007 @@ -13,9 +13,11 @@ 'WSGIFilter', 'HTTPEncode', 'OHM', + 'wsgi_intercept', ], dependency_links=[ 'http://codespeak.net/svn/z3/deliverance/trunk#egg=Deliverance-dev', + 'http://darcs.idyll.org/~t/projects/wsgi_intercept/wsgi_intercept.py#egg=wsgi_intercept-dev', ], packages=find_packages(), include_package_data=True, From ianb at codespeak.net Tue Jan 23 17:29:36 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Tue, 23 Jan 2007 17:29:36 +0100 (CET) Subject: [z3-checkins] r37214 - in z3/deliverance/DeliveranceVHoster/trunk/tests: . test-static test-static/blah Message-ID: <20070123162936.CCCCC1007F@code0.codespeak.net> Author: ianb Date: Tue Jan 23 17:29:34 2007 New Revision: 37214 Added: z3/deliverance/DeliveranceVHoster/trunk/tests/ z3/deliverance/DeliveranceVHoster/trunk/tests/conftest.py z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/ z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/blah/ z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/blah/foo.html z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/index.html z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/theme.html z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Log: Added functional test Added: z3/deliverance/DeliveranceVHoster/trunk/tests/conftest.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/conftest.py Tue Jan 23 17:29:34 2007 @@ -0,0 +1,4 @@ +import os, sys +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) +import pkg_resources +pkg_resources.require('DeliveranceVHoster') Added: z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/blah/foo.html ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/blah/foo.html Tue Jan 23 17:29:34 2007 @@ -0,0 +1,9 @@ + + +foo + + + +

foo

+ + Added: z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/index.html ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/index.html Tue Jan 23 17:29:34 2007 @@ -0,0 +1,15 @@ + + + + +Test Content + + + + +Unthemed stuff + +
+This is some content +
+ Added: z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/theme.html ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test-static/theme.html Tue Jan 23 17:29:34 2007 @@ -0,0 +1,13 @@ + + +Test Theme + + + +

Test Theme

+ +
replace me
+ +
This is a theme
+ + Added: z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Tue Jan 23 17:29:34 2007 @@ -0,0 +1,120 @@ +from paste.fixture import TestApp +from paste.urlparser import StaticURLParser +from dvhoster.wsgiapp import make_app +import wsgi_intercept + +wsgi_app = make_app( + {}, + data_dir=os.path.join(os.path.dirname(__file__), 'test-data')) +app = TestApp(wsgi_app) + +def put(uri, data): + app.post(uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}) + +rule_data = ''' + + + + + + + + + +''' + +static_app = StaticURLParser(os.path.join(os.path.dirname(__file__), + 'test-static')) +def make_static_app(): + return static_app + +wsgi_intercept.add_wsgi_intercept('wsgify.org', 80, make_static_app) + + +def test_everything(): + yield (create_api,) + yield (use_api, 'localhost') + yield (rename_site,) + yield (use_api, 'localhost2') + +def create_api(): + app = make_test_app() + + # Theme: + uri = 'http://wsgify.org/theme.html' + put('/.deliverance/theme_uri', uri) + res = app.get('/.deliverance/theme_uri') + assert res.body == uri + + # Rules: + put('/.deliverance/rules/rules.xml', rule_data) + + # Domain (no rename test): + res = app.get('/.deliverance/domain') + assert res.body == 'localhost' + + # Aliases: + data = "localhost.localhost\nexample.com" + put('/.deliverance/aliases', data) + assert app.get('/.deliverance/aliases').body == data + + data = ''' + [{"path": "/", "remote_uri": "http://wsgify.org/"}, + {"path": "/bar", "remote_uri": "http://wsgify.org/blah", comment="x"}] + ''' + + put('/.deliverance/remote_uris', data) + #assert app.get('/.deliverance/remote_uris').body == data + + data = ''' + [{"path": "/test1.html", "rewrite": "/test1"}, + {"prefix": "/test2", "rewrite": "/test3", "comment": "rename"}, + {"path": "/other.html", "rewrite": "http://otherexample.com/other.html"}] + ''' + put('/.deliverance/redirects', data) + + data = 'some data!' + put('/.deliverance/static/data.html', data) + res = app.get('/.deliverance/static/data.html') + assert res.data == data + app.get('/.deliverance/static/subdir', + extra_environ={'REQUEST_METHOD', 'MKCOL'}) + put('/.deliverance/static/subdir/foo.html', 'blah') + res = app.get('/.deliverance/static/subdir/foo.html') + assert res.body == 'blah' + res = app.get('/subdir/foo.html') + assert res.body == 'blah' + # Directories don't shadow like files: + res = app.get('/subdir/', status=404) + +def use_api(hostname): + app.extra_environ['HTTP_HOST'] = hostname + res = app.get('/index.html') + res.mustcontain( + # From the theme: + 'This is a theme', + # From the content: + 'This is some content') + # Should be dropped from theme: + assert 'replace' not in res + # Should be lost from content: + assert 'unthemed' not in res + # Redirect non-canonical domains: + res = app.get('/index.html', extra_environ=dict(HTTP_HOST='example.com'), + status=303) + assert res.header('location') == 'http://%s/index.html' % hostname + # Test the path backend redirect: + res = app.get('/bar/foo.html') + res.mustcontain('foo') + res = app.get('/test1.html', status=303) + assert res.header('location') == 'http://%s/test1' % hostname + res = app.get('/test2/other/stuff.html', status=303) + assert res.header('location') == 'http://%s/test3/other/stuff.html' % hostname + res = app.get('/test1.html/foo/bar', status=404) + res = app.get('/data.html') + res.mustcontain('some data!') + assert 'Test Theme' not in res + res = app.get('/subdir/foo.html') + assert res.body == 'blah' + + From ianb at codespeak.net Wed Jan 24 02:12:39 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 24 Jan 2007 02:12:39 +0100 (CET) Subject: [z3-checkins] r37244 - in z3/deliverance/DeliveranceVHoster/trunk: . dvhoster tests Message-ID: <20070124011239.72B6A1007D@code0.codespeak.net> Author: ianb Date: Wed Jan 24 02:12:31 2007 New Revision: 37244 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py z3/deliverance/DeliveranceVHoster/trunk/setup.cfg z3/deliverance/DeliveranceVHoster/trunk/tests/ (props changed) z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Log: Started the API, using OHM. Also functional tests (not all passing) for the API Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Wed Jan 24 02:12:31 2007 @@ -1,5 +1,13 @@ import re import os +from ohm import persist +from ohm import server +from ohm import descriptors +from ohm import lildav +from ohm.validators import LineConverter +from formencode import validators +from formencode.foreach import ForEach +from formencode.compound import All domain_re = re.compile(r'^localhost|[a-z][a-z0-9.\-]+\.[a-z]+$', re.I) @@ -48,7 +56,7 @@ dir = os.path.join(self.dir, domain_name) if not os.path.exists(dir): os.mkdir(dir) - return DomainDataProvider(domain_name, dir) + return DomainDataProvider(self, domain_name, dir) def normalize(self, domain_name): domain_name = domain_name.strip().lower() @@ -57,51 +65,22 @@ 'Bad domain name: %r' % domain_name) return domain_name -class file_property(object): - - def __init__(self, basename): - self.basename = basename - - def filename(self, obj): - return os.path.join(obj.dir, self.basename) - - def __get__(self, obj, type=None): - if obj is None: - return self - if not os.path.exists(self.filename(obj)): - return None - f = open(self.filename(obj), 'rb') - c = f.read() - f.close() - return c - - def __set__(self, obj, value): - f = open(self.filename(obj), 'wb') - f.write(value) - f.close() - - def __del__(self, obj): - os.unlink(self.filename(obj)) - - def __repr__(self): - return '%s(%r)' % ( - self.__class__.__name__, self.basename) - class DomainDataProvider(object): - def __init__(self, domain_name, dir): - self.domain_name = domain_name - self.dir = dir + def __init__(self, provider, domain_name, base_dir): + self._domain_name = domain_name + self.base_dir = base_dir + self.provider = provider def __repr__(self): return '<%s %s for %s in %r>' % ( self.__class__.__name__, hex(id(self)), - self.domain_name, - self.dir) + self._domain_name, + self.base_dir) def initialize(self): - for dir in [self.dir, self.rule_dir, + for dir in [self.base_dir, self.rule_dir, self.static_dir]: if not os.path.exists(dir): os.mkdir(dir) @@ -116,25 +95,99 @@ @property def initialized(self): - return bool(self.remote) + return hasattr(self, 'remote') @property def rule_dir(self): - return os.path.join(self.dir, 'rules') + return os.path.join(self.base_dir, 'rules') @property def static_dir(self): - return os.path.join(self.dir, 'static') - - remote = file_property('remote.txt') - theme_uri = file_property('theme_uri.txt') - theme_id = file_property('theme_id.txt') - rule_ids = file_property('rule_ids.txt') + return os.path.join(self.base_dir, 'static') + + remote = persist.file_property('remote.txt') + theme_uri = persist.file_property('theme_uri.txt') + theme_id = persist.file_property('theme_id.txt') + rule_ids = persist.file_property('rule_ids.txt') + aliases = descriptors.line_converter(persist.file_property('aliases.txt')) def set_rule_file(self, filename, content): filename = os.path.join(self.rule_dir, filename) f = open(filename, 'wb') f.write(content) f.close() - + def domain__get(self): + return self._domain_name + + def domain__set(self, value): + new_obj = self.provider.rename_domain(self.domain, value) + # Clone new object as self: + self.__dict__.update(new_obj.__dict__) + + domain = property(domain__get, domain__set) + +class DomainValidator(validators.Regex): + regex = r'^[a-z0-9\-]+|[a-z0-9][a-z0-9\-\.\_]*\.[a-z]+$' + +class ListDictValidator(validators.FancyValidator): + required_keys = () + optional_keys = () + + def validate_python(self, value, state=None): + # Should be like [{'path': path, 'remote_uri': uri, 'comment': str}] + # comment is optional + try: + assert isinstance(value, list), 'Must be list' + for d in value: + keys = d.keys() + for key in self.required_keys: + assert key in d, "Must have key %r in %r" % d + assert isinstance(d[key], basestring), ( + '%s must be string (not %r)' % (key, d[key])) + keys.remove(key) + for key in self.optional_keys: + if key not in d: + continue + assert isinstance(d[key], basestring), ( + '%s must be string (not %r)' % (key, d[key])) + keys.remove(key) + assert not keys, ( + "Key(s) not allowed: %s in %r" + % (', '.join(map(repr, keys)), d)) + except AssertionError, e: + raise validators.Invalid(str(e), value, state) + +class RemoteURIValidator(ListDictValidator): + required_keys = ('path', 'remote_uri') + optional_keys = ('comment', ) + +class RewriteValidator(ListDictValidator): + required_keys = ('rewrite', ) + # @@: Really we should require one of path or prefix + optional_keys = ('path', 'prefix', 'comment') + +class ProviderApp(server.ApplicationWrapper): + + theme_uri = server.Setter( + validator=validators.URL()) + domain = server.Setter( + validator=DomainValidator()) + aliases = server.Setter( + validator=All(ForEach(DomainValidator()), LineConverter())) + remote_uris = server.JSONSetter( + validator=RemoteURIValidator()) + redirects = server.JSONSetter( + validator=RewriteValidator()) + + @server.appfactory() + def rules(self): + if not os.path.exists(self.rule_dir): + os.makedirs(self.rule_dir) + return lildav.LilDAV(self.rule_dir) + + @server.appfactory() + def static(self): + if not os.path.exists(self.static_dir): + os.makedirs(self.static_dir) + return lildav.LilDAV(self.rule_dir) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Wed Jan 24 02:12:31 2007 @@ -8,7 +8,7 @@ from wsgifilter import proxyapp from wsgifilter import relocateresponse from deliverance.wsgimiddleware import DeliveranceMiddleware -from dvhoster.dataprovider import DataProvider +from dvhoster.dataprovider import DataProvider, ProviderApp from dvhoster import current_environ from dvhoster.debuginterp import Renderer @@ -38,20 +38,16 @@ environ, with_query_string=False, path_info='') path_info = norm_path(environ.get('PATH_INFO', '')) - if (not domain_info.initialized - or not domain_info.remote - or not domain_info.theme_uri): - new_url = construct_url( - environ, with_query_string=False, - path_info='/_deliverance') - exc = httpexceptions.HTTPTemporaryRedirect( - headers=[('location', new_url)]) - return exc(environ, start_response) + if path_info.startswith('/.deliverance'): + path_info_pop(environ) + subapp = ProviderApp(domain_info) + return subapp(environ, start_response) if path_info.startswith('/_rules'): path_info_pop(environ) subapp = StaticURLParser( domain_info.rule_dir) return subapp(environ, start_response) + static_path = os.path.join(domain_info.static_dir, path_info.lstrip('/')) static_fn = self.find_file(static_path) Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.cfg ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.cfg (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.cfg Wed Jan 24 02:12:31 2007 @@ -1,6 +1,6 @@ [egg_info] tag_build = dev -tag_svn_revision = true +#tag_svn_revision = true [easy_install] find_links = http://www.pylonshq.com/download/ Modified: z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Wed Jan 24 02:12:31 2007 @@ -1,15 +1,16 @@ +import os +import shutil from paste.fixture import TestApp from paste.urlparser import StaticURLParser from dvhoster.wsgiapp import make_app import wsgi_intercept -wsgi_app = make_app( - {}, - data_dir=os.path.join(os.path.dirname(__file__), 'test-data')) +data_filename = os.path.join(os.path.dirname(__file__), 'test-data') +wsgi_app = make_app({}, data_dir=data_filename) app = TestApp(wsgi_app) def put(uri, data): - app.post(uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}) + app.post(uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}, status=(201, 204)) rule_data = ''' @@ -32,14 +33,19 @@ def test_everything(): + yield (reset_env,) yield (create_api,) yield (use_api, 'localhost') yield (rename_site,) yield (use_api, 'localhost2') -def create_api(): - app = make_test_app() +def reset_env(): + if os.path.exists(data_filename): + shutil.rmtree(data_filename) + os.mkdir(data_filename) + +def create_api(): # Theme: uri = 'http://wsgify.org/theme.html' put('/.deliverance/theme_uri', uri) @@ -54,13 +60,13 @@ assert res.body == 'localhost' # Aliases: - data = "localhost.localhost\nexample.com" + data = "localhost.localhost\nexample.com\n" put('/.deliverance/aliases', data) assert app.get('/.deliverance/aliases').body == data data = ''' [{"path": "/", "remote_uri": "http://wsgify.org/"}, - {"path": "/bar", "remote_uri": "http://wsgify.org/blah", comment="x"}] + {"path": "/bar", "remote_uri": "http://wsgify.org/blah", "comment": "x"}] ''' put('/.deliverance/remote_uris', data) @@ -76,9 +82,9 @@ data = 'some data!' put('/.deliverance/static/data.html', data) res = app.get('/.deliverance/static/data.html') - assert res.data == data + assert res.body == data app.get('/.deliverance/static/subdir', - extra_environ={'REQUEST_METHOD', 'MKCOL'}) + extra_environ={'REQUEST_METHOD': 'MKCOL'}, status=201) put('/.deliverance/static/subdir/foo.html', 'blah') res = app.get('/.deliverance/static/subdir/foo.html') assert res.body == 'blah' @@ -88,7 +94,6 @@ res = app.get('/subdir/', status=404) def use_api(hostname): - app.extra_environ['HTTP_HOST'] = hostname res = app.get('/index.html') res.mustcontain( # From the theme: @@ -117,4 +122,10 @@ res = app.get('/subdir/foo.html') assert res.body == 'blah' - +def rename_site(): + put('/.deliverance/domain', 'localhost2') + app.extra_environ['HTTP_HOST'] = 'localhost2' + res = app.get('/.deliverance/aliases') + res.mustcontain('localhost') + assert app.get('/.deliverance/domain').body == 'localhost2' + From hannosch at codespeak.net Mon Jan 29 01:38:23 2007 From: hannosch at codespeak.net (hannosch at codespeak.net) Date: Mon, 29 Jan 2007 01:38:23 +0100 (CET) Subject: [z3-checkins] r37505 - in z3/jsonserver/branch/merge/concatresource: . compression compression/thirdparty Message-ID: <20070129003823.A9B8710060@code0.codespeak.net> Author: hannosch Date: Mon Jan 29 01:38:13 2007 New Revision: 37505 Modified: z3/jsonserver/branch/merge/concatresource/ (props changed) z3/jsonserver/branch/merge/concatresource/compression/ (props changed) z3/jsonserver/branch/merge/concatresource/compression/thirdparty/ (props changed) Log: Set svn:ignores, so the 'svn status' command provides some valuable information. From ltucker at codespeak.net Wed Jan 31 22:48:18 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 31 Jan 2007 22:48:18 +0100 (CET) Subject: [z3-checkins] r37694 - z3/deliverance/branches/cache_aware Message-ID: <20070131214818.7C64A10076@code0.codespeak.net> Author: ltucker Date: Wed Jan 31 22:48:15 2007 New Revision: 37694 Added: z3/deliverance/branches/cache_aware/ - copied from r37693, z3/deliverance/trunk/ Log: Create branch for experimental cache aware middleware From ltucker at codespeak.net Wed Jan 31 22:53:07 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 31 Jan 2007 22:53:07 +0100 (CET) Subject: [z3-checkins] r37695 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070131215307.A6D0110077@code0.codespeak.net> Author: ltucker Date: Wed Jan 31 22:53:04 2007 New Revision: 37695 Added: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: primary changes to support caching Added: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- (empty file) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Wed Jan 31 22:53:04 2007 @@ -0,0 +1,487 @@ +import re +from paste.response import header_value, replace_header +from sets import Set + + +""" +utilities for fusing cache related HTTP headers from +multiple sources + +XXX there is probably a good amount of work in here +that Paste could simplify + +TODO: +handle expires +handle last-modified +""" + + +def merge_cache_headers(self, response_info, new_headers): + """ + replaces cache related headers in new_headers + with caching info calculated cache_info + (a map of urls to wsgi response triples) + """ + + cache_info = {} + for uri, response in response_info.items(): + cache_info[uri] = response[1] + + cache_control = merge_cache_control(cache_info.values()) + if cache_control: + replace_header(new_headers, 'cache-control', cache_control) + + etag = merge_etags_from_headers(cache_info) + if etag is not None: + replace_header(new_headers, 'etag', etag ) + + vary = merge_vary_from_headers(cache_info) + if vary is not None: + replace_header(new_headers, 'vary', vary) + + # XXX Expires + + + + +def merge_cache_control(header_sets): + """ + computes a value for the cache-control header based on the + values of the cache-control headers found in the list of + wsgi-style response header lists. + + >>> headerses = [] + >>> headerses.append([ ('cache-control', "public, max-age = 10") ]) + >>> headerses.append([ ('cache-control', "public, max-age = 5") ]) + >>> headerses.append([ ('cache-control', "public, max-age = 2") ]) + >>> merge_cache_control(headerses) + 'public, max-age = 2' + + >>> headerses = [] + >>> headerses.append([ ('cache-control', "public, max-age = 10") ]) + >>> headerses.append([ ('cache-control', "private, max-age = 5") ]) + >>> headerses.append([ ('cache-control', "public, max-age = 2") ]) + >>> merge_cache_control(headerses) + 'private, max-age = 2' + + """ + + cache_ctls = [parse_cache_directives(header_value(x,'cache-control')) for x in header_sets] + + # apply cache-control merging policies + new_cache_ctl = dict() + merge_if_all('public',new_cache_ctl, cache_ctls) + merge_if_any('private',new_cache_ctl, cache_ctls) + merge_if_any('private',new_cache_ctl, cache_ctls) + merge_if_any('no-cache',new_cache_ctl, cache_ctls) + merge_if_any('no-store',new_cache_ctl, cache_ctls) + merge_if_any('no-transform', new_cache_ctl, cache_ctls) + merge_if_any('must-revalidate', new_cache_ctl, cache_ctls) + merge_if_any('proxy-revalidate', new_cache_ctl, cache_ctls) + merge_minimum('max-age', new_cache_ctl, cache_ctls) + merge_minimum('smax-age', new_cache_ctl, cache_ctls) + + return flatten_directive_map(new_cache_ctl) + + +def merge_etags_from_headers(headers_map): + """ + accepts a map from uris to wsgi-style header lists + returns the value for the etag merged from all + etag headers present in the header lists + """ + etag_map = {} + for uri, headers in headers_map.items(): + etag = header_value(headers,'etag') + if etag is not None and len(etag) != 0: + etag_map[uri] = etag + return merge_etags(etag_map) + + +def merge_vary_from_headers(headers_map): + """ + XXX set ordering + >>> d = {'a': [ ('Vary', '"foo, bar"') ], 'b': [ ('Vary', '"bar, quux"') ]} + >>> merge_vary_from_headers(d) + '"quux, foo, bar"' + + >>> d = {} + >>> v = merge_vary_from_headers(d) + >>> v is None + True + + """ + vary_fields = Set() + for val in [ header_value(x, 'vary') for x in headers_map.values() ]: + vary_fields.update(parse_fieldname_list(val)) + + if len(vary_fields): + return '\"%s\"' % ', '.join(vary_fields) + else: + return None + + +def parse_merged_etag(composite_tag): + """ + given a composite etag computed by merge_etags, + computes a map from resource identifiers to + respective etags + + >>> d = parse_merged_etag('deliverance:apple,15,some_apple_etag,orange,16,some_orange_etag') + >>> print_sorted_dict(d) + {'apple': 'some_apple_etag', 'orange': 'some_orange_etag'} + + + >>> d = parse_merged_etag('some_raND0m_g0bbl7+yGook') + >>> d + {} + + >>> d = parse_merged_etag('deliverance:some_,99,ra,ND0m_g0,bb,l7+yGook') + >>> d + {} + + """ + if not composite_tag.startswith('deliverance:'): + return {} + + tags = dict(); + + composite_tag = composite_tag[len('deliverance:'):] + while len(composite_tag) > 0: + resource,composite_tag = pop_et_token(composite_tag) + if resource is None: + return tags + tag_len, composite_tag = pop_et_token(composite_tag) + if tag_len is None: + return tags + try: + tag_len = int(tag_len) + except: + return {} + + if len(composite_tag) >= tag_len: + tags[resource] = composite_tag[:tag_len] + composite_tag = composite_tag[tag_len+1:] + else: + return {} + + return tags + + + +############# +# helpers +############# + + +def pop_et_token(ctag): + """ + finds the first comma separated token, returns a tuple + containing the token and the rest of the string given + + >>> pop_et_token("abc,def,ghi") + ('abc', 'def,ghi') + """ + sep = ctag.find(',') + if sep == -1: + return (None,ctag) + else: + return (ctag[:sep],ctag[sep+1:]) + + + + +CSL_QUOTE_PAT = '".*?"' +def parse_header_list(hval): + """ + split comma separated list into elements, ignoring quoted + commas. + eg: + + >>> parse_header_list('max-age = 10, public') + ['max-age = 10', 'public'] + + >>> parse_header_list('max-age = 10, public = "foo, bar"') + ['max-age = 10', 'public = "foo, bar"'] + + >> parse_header_list('public') + ['public'] + """ + quoted_strings = re.findall(CSL_QUOTE_PAT,hval) + no_quote_val = re.sub(CSL_QUOTE_PAT,'?',hval) + vals = [x.strip() for x in no_quote_val.split(',')] + + for i,val in enumerate(vals): + qpos = val.find('?') + if qpos != -1: + vals[i] = val.replace('?',quoted_strings.pop()) + + return vals + + +def parse_cache_directive(directive): + """ + returns a tuple for the directive containing the name of + the directive and a list of arguments. eg: + + >>> parse_cache_directive('foo = 10') + ('foo', '10') + + >>> parse_cache_directive('foo = "bar"') + ('foo', '"bar"') + + >>> parse_cache_directive('foo = "bar, quux, baz"') + ('foo', '"bar, quux, baz"') + + >>> parse_cache_directive("foo") + ('foo', None) + """ + split = directive.find('=') + if (split == -1): + return (directive,None) + else: + return (directive[0:split].strip(), + directive[split+1:].strip()) + +def parse_fieldname_list(val): + """ + parses directive value(s) into a list, eg: + + >>> parse_fieldname_list('foo') + ['foo'] + + >>> parse_fieldname_list('"foo"') + ['foo'] + + >>> parse_fieldname_list('"foo, bar,quux"') + ['foo', 'bar', 'quux'] + + >>> parse_fieldname_list('""') + [] + + >>> parse_fieldname_list(None) + [] + """ + + if val is None: + return [] + + if val.startswith('"'): + val = val[1:] + if val.endswith('"'): + val = val[:-1] + val = val.strip() + + if len(val) == 0: + return [] + + return [x.strip().lower() for x in val.split(',')] + + +def parse_cache_directives(hval): + """ + returns a dict mapping directives to raw values + + >>> print_sorted_dict(parse_cache_directives('max-age = 10, public')) + {'max-age': '10', 'public': None} + + >>> print_sorted_dict(parse_cache_directives('max-age = 10, public = "foo, bar"')) + {'max-age': '10', 'public': '"foo, bar"'} + """ + if hval is None: + return {} + + dirs = dict() + for (name,val) in [parse_cache_directive(x) for x in parse_header_list(hval)]: + dirs[name] = val + return dirs + +def merge_expire_header(cc, headers): + """ + this reformulates any expire header in headers and + places an equivalent cache-control header in cc + """ + pass + +def merge_etags(etag_map): + """ + given a map of resource identifiers to etags, + computes a composite etag + + XXX dict ordering + >>> d = {'apple': 'some_apple_etag', 'orange': 'some_orange_etag'} + >>> merge_etags(d) + 'deliverance:orange,16,some_orange_etag,apple,15,some_apple_etag' + """ + if etag_map is None or len(etag_map) == 0: + return None + + composite_etag="deliverance:" + + for k,v in etag_map.items(): + composite_etag += "%s,%d,%s," % (k,len(v),v) + composite_etag = composite_etag[:-1] + return composite_etag + + + +def merge_if_all(directive, newcc, cc): + """ + puts the directive given in the new cache-control + directives newcc if the directive appears in all + sets of directives cc + + expects cc is a list of dicts of the form produced by + parse_cache_directives + eg: + + >>> d = dict() + >>> ccs = [{'public': None, 'max-age': '10'}, {'public': None, 'max-age': '20'}] + >>> merge_if_all('public',d,ccs) + >>> d + {'public': None} + + >>> d = dict() + >>> ccs = [{'public': None, 'max-age': '10'}, {'max-age': '20'}] + >>> merge_if_all('public', d, ccs) + >>> d + {} + """ + for c in cc: + if not c.has_key(directive): + return + newcc[directive] = None + +def merge_if_any(directive, newcc, cc): + """ + puts the directive given in the new cache-control + directives newcc if the directive appears in any of + the sets of directives cc. merges any fieldname + lists that appear in cc for the directive. if any + instance has no fieldnames, no fieldnames are used + in the output. + + expects cc is a list of dicts of the form produced by + parse_cache_directives + + >>> d = dict() + >>> ccs = [{'private': None, 'max-age': '10'}, {'max-age': '20'}] + >>> merge_if_any('private', d, ccs) + >>> d + {'private': None} + + >>> d = dict() + >>> ccs = [{'private': '"foo, bar"', 'max-age': '10'}, {'max-age': '9'}, {'private': '"quux, bar"'}] + >>> merge_if_any('private', d, ccs) + >>> d + {'private': '"quux, foo, bar"'} + + >>> d = dict() + >>> ccs = [{'private': '"foo, bar"', 'max-age': '10'}, {'max-age': '20'}, {'private': None}] + >>> merge_if_any('private', d, ccs) + >>> d + {'private': None} + + """ + present = False + field_set = Set() + + for c in cc: + if c.has_key(directive): + present = True + if c[directive] is not None: + if field_set is not None: + field_set.update(parse_fieldname_list(c[directive])) + else: + field_set = None + + if present: + if field_set and len(field_set): + newcc[directive] = '"' + ', '.join(field_set) + '"' + else: + newcc[directive] = None + +def merge_minimum(directive, newcc, cc): + """ + puts the minimum value specified for the directive + among all instances of the directive in the set cc + into the dict newcc. + if the directive does not appear in a particular + set, the value is not placed in newcc. + + expects cc is a list of dicts of the form produced by + parse_cache_directives + + >>> d = dict() + >>> ccs = [{'max-age': '10'}, {'max-age': '20'} ] + >>> merge_minimum('max-age', d, ccs) + >>> d + {'max-age': '10'} + + >>> d = dict() + >>> ccs = [{'max-age': '10'}, {'smax-age': '20'} ] + >>> merge_minimum('max-age', d, ccs) + >>> d + {} + """ + + if len(cc) == 0: + return + + if cc[0].has_key(directive): + min = int(cc[0][directive]) + else: + return + + for c in cc: + if c.has_key(directive): + dval = int(c[directive]) + if dval < min: + min = dval + else: + return + + newcc[directive] = str(min) + +def flatten_directive_map(d): + """ + flattens a map of directive -> fieldnames + back into the HTTP comma separated list + form suitable as a value for the + cache-control header + """ + dstr = '' + last = len(d) -1 + for i, k in enumerate(d.keys()): + dstr += k + if d[k]: + dstr += ' = %s' % d[k] + if (i != last): + dstr += ', ' + + return dstr + + +######################### +# just test support +######################### + +def print_sorted_dict(d): + keys = d.keys() + keys.sort() + last = len(keys)-1 + dstr = '{' + for i, k in enumerate(keys): + dstr += "%s: %s" % (k.__repr__(), d[k].__repr__()) + if i < last: + dstr += ', ' + dstr += '}' + print dstr + + +def _test(): + import doctest + doctest.testmod() + +if __name__ == "__main__": + _test() Added: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py ============================================================================== --- (empty file) +++ z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Wed Jan 31 22:53:04 2007 @@ -0,0 +1,128 @@ +import deliverance.wsgimiddleware +from StringIO import StringIO +from paste.wsgilib import intercept_output +from paste.proxy import TransparentProxy +from paste.request import construct_url +from paste.response import header_value +import urlparse +from deliverance.utils import DeliveranceError + + +class InternalResourceFetcher(object): + def __init__(self, in_environ, uri, app, headers_only=False): + self.uri = uri + self.app = app + + if 'paste.recursive.include' in in_environ: + self.environ = in_environ['paste.recursive.include'].original_environ.copy() + else: + self.environ = in_environ.copy() + + if not self.uri.startswith('/'): + self.uri = '/' + self.uri + + self.environ['PATH_INFO'] = uri + + base_url = in_environ['deliverance.base-url'] + if base_url is not None: + self.environ['SCRIPT_NAME'] = urlparse.urlparse(base_url)[2] + else: + self.environ['SCRIPT_NAME'] = '' + + if headers_only: + self.environ['REQUEST_METHOD'] = 'HEAD' + else: + self.environ['REQUEST_METHOD'] = 'GET' + + self.environ['CONTENT_LENGTH'] = '0' + self.environ['wsgi.input'] = StringIO('') + self.environ['CONTENT_TYPE'] = '' + self.environ['QUERY_STRING'] = 'notheme' + + if 'HTTP_ACCEPT_ENCODING' in self.environ: + self.environ['HTTP_ACCEPT_ENCODING'] = '' + + def wsgi_get(self): + print "Internal Resource get: %s" % self.uri + if 'paste.recursive.include' in self.environ: + print "Doing paste.recursive.include" + # Try to do the redirect this way... + includer = self.environ['paste.recursive.include'] + res = includer(self.uri, self.environ) + return (res.status, res.headers, res.body) + else: + print "Doing intercept" + return intercept_output(self.environ, self.app) + + + def get(self): + path_info = self.environ['PATH_INFO'] + status, headers, body = self.wsgi_get() + + if not status.startswith('200'): + loc = header_value(headers, 'location') + if loc: + loc = ' location=%r' % loc + else: + loc = '' + raise DeliveranceError( + "Request for internal resource at %s (%r) failed with status code %r%s" + % (construct_url(self.environ), path_info, status, + loc)) + return body + + +class ExternalResourceFetcher(object): + def __init__(self, uri, headers_only=False): + self.uri = uri + + url_chunks = urlparse.urlsplit(uri) + loc = urlparse.urlsplit(uri) + + self.environ = {} + + if headers_only: + self.environ['REQUEST_METHOD'] = 'HEAD' + else: + self.environ['REQUEST_METHOD'] = 'GET' + + self.environ['CONTENT_LENGTH'] = '0' + self.environ['wsgi.input'] = StringIO('') + + self.environ['wsgi.url_scheme'] = loc[0] + self.environ['wsgi.version'] = (1, 0) + self.environ['HTTP_HOST'] = loc[1] + self.environ['PATH_INFO'] = loc[2] + self.environ['QUERY_STRING'] = loc[3] + + self.environ['SCRIPT_INFO'] = '' + + #if loc[0].find(':') != -1: + # self.environ['SERVER_NAME'],self.environ['SERVER_PORT'] = loc[0].split(':') + #else: + # self.environ['SERVER_NAME'] = loc[0] + # if loc[0] == 'https': + # self.environ['SERVER_PORT'] = '443' + # else: + # self.environ['SERVER_PORT'] = '80' + + def wsgi_get(self): + print "External Resource get: %s" % self.uri + proxy_app = TransparentProxy() + return intercept_output(self.environ, proxy_app) + + def get(self): + status, headers, body = self.wsgi_get() + + if not status.startswith('200'): + loc = header_value(headers, 'location') + if loc: + loc = ' location=%r' % loc + else: + loc = '' + raise DeliveranceError( + "Request for external resource at %s failed with status code %r%s" + % (construct_url(self.environ), status, + loc)) + + return body Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Jan 31 22:53:04 2007 @@ -13,14 +13,22 @@ from htmlserialize import tostring from deliverance.utils import DeliveranceError from deliverance.utils import DELIVERANCE_ERROR_PAGE +from deliverance.resource_fetcher import InternalResourceFetcher, ExternalResourceFetcher +from deliverance import cache_utils import sys import datetime import threading import traceback from StringIO import StringIO +from sets import Set DELIVERANCE_BASE_URL = 'deliverance.base-url' +DELIVERANCE_CACHE = 'deliverance.cache' +IGNORE_EXTENSIONS = ['js','css','gif','jpg','jpeg','pdf','ps','doc','png','ico','mov','mpg','mpeg', 'mp3','m4a', + 'txt','rtf'] + +IGNORE_URL_PATTERN = re.compile("^.*\.(%s)$" % '|'.join(IGNORE_EXTENSIONS)) class DeliveranceMiddleware(object): """ @@ -58,7 +66,7 @@ else: self._rendererType = renderer - def get_renderer(self,environ): + def get_renderer(self, environ): """ retrieve the deliverance Renderer representing the transformation this middlware represents. Renderer may change according to caching rules. @@ -72,7 +80,7 @@ finally: self._lock.release() - def create_renderer(self,environ): + def create_renderer(self, environ): """ construct a new deliverance Renderer from the information passed to the initializer. A new copy @@ -85,7 +93,7 @@ self.theme_uri) def reference_resolver(href, parse, encoding=None): - text = self.get_resource(environ,href) + text = self.get_resource(environ, href) if parse == "xml": return etree.XML(text) if parse == "html": @@ -132,7 +140,7 @@ initializer. """ try: - return self.get_resource(environ,self.rule_uri) + return self.get_resource(environ, self.rule_uri) except Exception, message: newmessage = "Unable to retrieve rules from " + self.rule_uri if message: @@ -146,7 +154,7 @@ initializer. """ try: - return self.get_resource(environ,self.theme_uri) + return self.get_resource(environ, self.theme_uri) except Exception, message: newmessage = "Unable to retrieve theme page from " + self.theme_uri if message: @@ -165,27 +173,37 @@ try: qs = environ.get('QUERY_STRING', '') environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) + environ[DELIVERANCE_CACHE] = {} notheme = 'notheme' in qs if notheme: return self.app(environ, start_response) - if 'HTTP_ACCEPT_ENCODING' in environ: - del environ['HTTP_ACCEPT_ENCODING'] - status, headers, body = intercept_output( - environ, self.app, - self.should_intercept, - start_response) + # unsupported + if 'HTTP_ACCEPT_ENCODING' in environ: + environ['HTTP_ACCEPT_ENCODING'] = '' + if 'HTTP_IF_MATCH' in environ: + environ['HTTP_IF_MATCH'] = '' + if 'HTTP_IF_UNMODIFIED_SINCE' in environ: + environ['HTTP_IF_UNMODIFIED_SINCE'] = '' + + status, headers, body = self.rebuild_check(environ, start_response) - # ignore non-html responses + # non-html responses, or rebuild is not necessary: bail out if status is None: return body - # don't theme html snippets - if self.hasHTMLTag(body): - body = self.filter_body(environ, body) + # perform actual themeing + print "Doing themeing" + + body = self.filter_body(environ, body) replace_header(headers, 'content-length', str(len(body))) replace_header(headers, 'content-type', 'text/html; charset=utf-8') + + cache_utils.merge_cache_headers(environ, + environ[DELIVERANCE_CACHE], + headers) + start_response(status, headers) return [body] @@ -209,7 +227,7 @@ """ type = header_value(headers, 'content-type') if type is None: - return False + return True # yerg, 304s can have no content-type return type.startswith('text/html') or type.startswith('application/xhtml+xml') def filter_body(self, environ, body): @@ -220,76 +238,133 @@ content = self.get_renderer(environ).render(parseHTML(body)) return tostring(content) - def get_resource(self, environ, uri): - """ - retrieve the data referred to by the uri given. - """ - internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) - uri = urlparse.urljoin(internalBaseURL, uri) - - if internalBaseURL and uri.startswith(internalBaseURL): - return self.get_internal_resource(environ, uri[len(internalBaseURL):]) - else: - return self.get_external_resource(uri) - def relative_uri(self, uri): - """ - returns true if uri is relative, false if - the uri is absolute. - """ - if re.search(r'^[a-zA-Z]+:', uri): - return False - else: - return True + def rebuild_check(self, environ, start_response): + print "===== rebuild check =====" + # perform the request for content - def get_external_resource(self, uri): - """ - get the data referred to by the uri given - using urllib (not through the wrapped app) - """ - f = urllib.urlopen(uri) - content = f.read() - f.close() - return content + content_url = construct_url(environ) - def get_internal_resource(self, in_environ, uri): - """ - get the data referred to by the uri given - by using the wrapped WSGI application - """ + status, headers, body = intercept_output(environ, self.app, + self.should_intercept, + start_response) + + + if status is None: + # should_intercept says this isn't HTML, we're done + print "ignore non-html: %s" % construct_url(environ) + return (None, None, body) + if self.should_ignore_url(content_url): + print "ignore blacklisted url: %s" % construct_url(environ) + start_response(status, headers) + return (None, None, [body]) + + # cache the response so we can look at its headers later + environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) - if 'paste.recursive.include' in in_environ: - environ = in_environ['paste.recursive.include'].original_environ.copy() - else: - environ = in_environ.copy() + # it was modified or an error, give it back for themeing + if not status.startswith('304'): + print "Content %s modified, continue..." % content_url + + # if it's not a full HTML page, skip it + if not self.hasHTMLTag(body): + print "ignore non-html-tagged: %s" % construct_url(environ) + start_response(status, headers) + return (None, None, [body]) + + # send it back for rebuild + return (status, headers, body) - if not uri.startswith('/'): - uri = '/' + uri - environ['PATH_INFO'] = uri - environ['SCRIPT_NAME'] = in_environ[DELIVERANCE_BASE_URL] - environ['REQUEST_METHOD'] = 'GET' - environ['CONTENT_LENGTH'] = '0' - environ['wsgi.input'] = StringIO('') - environ['CONTENT_TYPE'] = '' - if environ['QUERY_STRING']: - environ['QUERY_STRING'] += '¬heme' - else: - environ['QUERY_STRING'] = 'notheme' + # got 304 Not Modified for content, check other resources + rules = etree.XML(self.rule(environ)) + resources = self.get_resource_uris(rules) + if self.any_modified(environ, resources): + # something changed, + # get the content explicitly and give it back + print "explicitly requesting %s" % construct_url(environ) + if 'HTTP_IF_MODIFIED_SINCE' in environ: + environ['HTTP_IF_MODIFIED_SINCE'] = '' + if 'HTTP_IF_NONE_MATCH' in environ: + environ['HTTP_IF_NONE_MATCH'] = '' + environ['CACHE-CONTROL'] = 'no-cache' + + status, headers, body = intercept_output(environ, self.app) + + if not self.hasHTMLTag(body): + # XXX yarg, we didn't care about it! + print "ARG ignore non-html: status: %s, %s" % (status, construct_url(environ)) + #print "Environ: " , environ , " Headers: ", headers + start_response(status, headers) + return (None, None, [body]) + + environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) + return (status, headers, body) + + # nothing was modified, give back a 304 + print "giving back 304: %s" % construct_url(environ) + cache_utils.merge_cache_headers(environ, + environ[DELIVERANCE_CACHE], + headers) + start_response('304 Not Modified', headers) - if 'HTTP_ACCEPT_ENCODING' in environ: - environ['HTTP_ACCEPT_ENCODING'] = '' + return (None,None,[]) + + def any_modified(self, environ, resources): + """ + returns a tuple containing a boolean and map of uris to HTTP response headers. + the first value represents whether any resource in resources has been + modified based on the checks contained in environ. The uris in the list + resources are associated with their respective response headers in the + second element of the tuple. + """ + + print "====== rebuild check ======" + moddate = None + etag_map = {} + + if 'HTTP_IF_MODIFIED_SINCE' in environ: + print "using modification date: %s" % environ['HTTP_IF_MODIFIED_SINCE'] + moddate = environ['HTTP_IF_MODIFIED_SINCE'] + if 'HTTP_IF_NONE_MATCH' in environ: + print "using composite etag: %s" % environ['HTTP_IF_NONE_MATCH'] + etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + + for uri in resources: + if (self.check_modification(environ, uri, + moddate, + etag_map.get(uri,None))): + return True - if 'paste.recursive.include' in in_environ: - # Try to do the redirect this way... - includer = in_environ['paste.recursive.include'] - res = includer(uri,environ) - return res.body + return False - path_info = environ['PATH_INFO'] - status, headers, body = intercept_output(environ, self.app) - if not status.startswith('200'): + def get_resource(self, environ, uri): + """ + retrieve the content from the uri given, + uses cache if possible. throws exception if + response is not 200 + """ + if uri in environ[DELIVERANCE_CACHE]: + response = environ[DELIVERANCE_CACHE][uri] + if response[0].startswith('200'): + print "using previously fetched content for %s" % uri + return response[2] + + print "fetching resource from scratch: %s" % uri + fetcher = self.get_fetcher(environ, uri) + + # eliminate validation headers, we want the content + if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: + fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = '' + if 'HTTP_IF_NONE_MATCH' in fetcher.environ: + fetcher.environ['HTTP_IF_NONE_MATCH'] = '' + fetcher.environ['CACHE-CONTROL'] = 'no-cache' + + status, headers, body = fetcher.wsgi_get() + + if not status.startswith('200'): + path_info = uri loc = header_value(headers, 'location') if loc: loc = ' location=%r' % loc @@ -299,7 +374,79 @@ "Request for internal resource at %s (%r) failed with status code %r%s" % (construct_url(environ), path_info, status, loc)) + + environ[DELIVERANCE_CACHE][uri] = (status, headers, body) + return body + + + def get_fetcher(self, environ, uri): + internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) + uri = urlparse.urljoin(internalBaseURL, uri) + + if internalBaseURL and uri.startswith(internalBaseURL): + return InternalResourceFetcher(environ, uri[len(internalBaseURL):], + self.app) + else: + return ExternalResourceFetcher(uri) + + + def get_resource_uris(self, rules): + """ + retrieves a list of uris pointing to the resources that + are components of rendering (excluding content) + """ + resources = Set() + resources.add(self.rule_uri) + resources.add(self.theme_uri) + + for rule in rules: + href = rule.get("href",None) + if href is not None: + resources.add(href) + + return list(resources) + + + def check_modification(self, environ, uri, httpdate_since=None, etag=None): + """ + if httpdate_since is set to an httpdate the If-Modified-Since HTTP header + is used to check for modification + + if etag is set to an etag for the resource, the If-None-Match HTTP header + is used to check for modification + + """ + + print "[!] Checking modification for: [%s] w/ [%s,%s]" % (uri, httpdate_since, etag) + + fetcher = self.get_fetcher(environ, uri) + + if httpdate_since: + fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = httpdate_since + else: + fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = '' + + + if etag: + fetcher.environ['HTTP_IF_NONE_MATCH'] = etag + else: + fetcher.environ['HTTP_IF_NONE_MATCH'] = '' + + + status, headers, body = fetcher.wsgi_get() + environ[DELIVERANCE_CACHE][uri] = (status, headers, body) + + print "status was: [%s]" % status + if not (status.startswith('200') or status.startswith('304')): + print "status(%s), environ => %s, headers => %s" % (status, fetcher.environ, headers) + + if status.startswith('304'): # Not Modified + return False + + return True + + HTML_DOC_PAT = re.compile(r"^.*<\s*html(\s*|>).*$",re.I|re.M) def hasHTMLTag(self, body): @@ -311,6 +458,11 @@ """ return self.HTML_DOC_PAT.search(body) is not None + + def should_ignore_url(self, url): + # blacklisting can happen here as well + return re.match(IGNORE_URL_PATTERN, url) is not None + def make_filter(app, global_conf, theme_uri=None, rule_uri=None): assert theme_uri is not None, ( From ltucker at codespeak.net Thu Feb 1 16:35:35 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Thu, 1 Feb 2007 16:35:35 +0100 (CET) Subject: [z3-checkins] r37746 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070201153535.C031C100AA@code0.codespeak.net> Author: ltucker Date: Thu Feb 1 16:35:32 2007 New Revision: 37746 Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Log: support query string on internal resources Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Thu Feb 1 16:35:32 2007 @@ -21,7 +21,13 @@ if not self.uri.startswith('/'): self.uri = '/' + self.uri - self.environ['PATH_INFO'] = uri + uri_parts = urlparse.urlparse(uri) + + self.environ['PATH_INFO'] = uri_parts[2] + if len(uri_parts[4]) > 0: + self.environ['QUERY_STRING'] = uri_parts[4] + '¬heme' + else: + self.environ['QUERY_STRING'] = 'notheme' base_url = in_environ['deliverance.base-url'] if base_url is not None: @@ -37,7 +43,7 @@ self.environ['CONTENT_LENGTH'] = '0' self.environ['wsgi.input'] = StringIO('') self.environ['CONTENT_TYPE'] = '' - self.environ['QUERY_STRING'] = 'notheme' + if 'HTTP_ACCEPT_ENCODING' in self.environ: self.environ['HTTP_ACCEPT_ENCODING'] = '' @@ -52,7 +58,9 @@ return (res.status, res.headers, res.body) else: print "Doing intercept" - return intercept_output(self.environ, self.app) + status, headers, body = intercept_output(self.environ, self.app) + print " => %s" % status + return (status, headers, body) def get(self): From ltucker at codespeak.net Fri Feb 2 00:20:23 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 2 Feb 2007 00:20:23 +0100 (CET) Subject: [z3-checkins] r37778 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070201232023.0FD441007E@code0.codespeak.net> Author: ltucker Date: Fri Feb 2 00:20:20 2007 New Revision: 37778 Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Log: treats headers with only Expires set as cache-control: max-age headers Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/cache_utils.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Fri Feb 2 00:20:20 2007 @@ -1,5 +1,7 @@ import re from paste.response import header_value, replace_header +from paste.httpheaders import EXPIRES +from time import time as now from sets import Set @@ -23,50 +25,77 @@ (a map of urls to wsgi response triples) """ - cache_info = {} + headers_map = {} for uri, response in response_info.items(): - cache_info[uri] = response[1] - - cache_control = merge_cache_control(cache_info.values()) - if cache_control: - replace_header(new_headers, 'cache-control', cache_control) + headers_map[uri] = response[1] + + cache_control_map = merge_cache_control(headers_map.values(), upgrade_expires=True) + if len(cache_control_map): + replace_header(new_headers, 'cache-control', + flatten_directive_map(cache_control_map)) + # provide an Expires header if there is a cache-control max-age + if 'max-age' in cache_control_map: + expire_delta = int(new_cache_ctl['max-age']) + EXPIRES.update(new_headers, delta=expire_delta) - etag = merge_etags_from_headers(cache_info) + etag = merge_etags_from_headers(headers_map) if etag is not None: replace_header(new_headers, 'etag', etag ) - vary = merge_vary_from_headers(cache_info) + vary = merge_vary_from_headers(headers_map) if vary is not None: replace_header(new_headers, 'vary', vary) - # XXX Expires - - -def merge_cache_control(header_sets): +def merge_cache_control(header_sets, upgrade_expires=False): """ computes a value for the cache-control header based on the values of the cache-control headers found in the list of - wsgi-style response header lists. + wsgi-style response header lists. returns a map of + cache-control directive names to values. use + flatten_directive_map to compute value suitable for + the cache-control header. + + if upgrade_expires is True, if there is a header set with + an Expires header, but no cache-control header, it is treated as a + cache-control: public, max-age: >>> headerses = [] >>> headerses.append([ ('cache-control', "public, max-age = 10") ]) >>> headerses.append([ ('cache-control', "public, max-age = 5") ]) >>> headerses.append([ ('cache-control', "public, max-age = 2") ]) - >>> merge_cache_control(headerses) + >>> flatten_directive_map(merge_cache_control(headerses)) 'public, max-age = 2' >>> headerses = [] >>> headerses.append([ ('cache-control', "public, max-age = 10") ]) >>> headerses.append([ ('cache-control', "private, max-age = 5") ]) >>> headerses.append([ ('cache-control', "public, max-age = 2") ]) - >>> merge_cache_control(headerses) + >>> flatten_directive_map(merge_cache_control(headerses)) 'private, max-age = 2' - """ + >>> headerses = [[],[('cache-control', "public, max-age = 100")]] + >>> from paste.httpheaders import EXPIRES + >>> EXPIRES.update(headerses[0], time=( int(now()) + 20)) + >>> flatten_directive_map(merge_cache_control(headerses, upgrade_expires=True)) + 'public, max-age = 20' + + """ + cache_ctls = [parse_cache_directives(header_value(x,'cache-control')) for x in header_sets] + + if upgrade_expires: + # if there is a header set with no cache-control, but an Expires, + # upgrade it to a cache-control: max-age = , public + for i,cc in enumerate(cache_ctls): + if len(cc) == 0: + expire_val = header_value(header_sets[i],'expires') + if expire_val is not None: + expire_secs = int(EXPIRES.parse(expire_val) - int(now())) + cc['public'] = None + cc['max-age'] = expire_secs # apply cache-control merging policies new_cache_ctl = dict() @@ -81,7 +110,7 @@ merge_minimum('max-age', new_cache_ctl, cache_ctls) merge_minimum('smax-age', new_cache_ctl, cache_ctls) - return flatten_directive_map(new_cache_ctl) + return new_cache_ctl def merge_etags_from_headers(headers_map): @@ -120,7 +149,6 @@ else: return None - def parse_merged_etag(composite_tag): """ given a composite etag computed by merge_etags, From ltucker at codespeak.net Fri Feb 2 16:19:33 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 2 Feb 2007 16:19:33 +0100 (CET) Subject: [z3-checkins] r37811 - z3/deliverance/DeliveranceDemo/trunk Message-ID: <20070202151933.F0E3410081@code0.codespeak.net> Author: ltucker Date: Fri Feb 2 16:19:31 2007 New Revision: 37811 Modified: z3/deliverance/DeliveranceDemo/trunk/setup.py Log: add dependency link for Deliverance Modified: z3/deliverance/DeliveranceDemo/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/setup.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/setup.py Fri Feb 2 16:19:31 2007 @@ -14,6 +14,9 @@ 'WSGIFilter', 'HTTPEncode', ], + dependency_links=[ + 'http://codespeak.net/svn/z3/deliverance/trunk#egg=Deliverance-dev' + ], packages=find_packages(), include_package_data=True, test_suite = 'nose.collector', From ltucker at codespeak.net Fri Feb 2 20:17:32 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 2 Feb 2007 20:17:32 +0100 (CET) Subject: [z3-checkins] r37818 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070202191732.4970D10063@code0.codespeak.net> Author: ltucker Date: Fri Feb 2 20:17:28 2007 New Revision: 37818 Added: z3/deliverance/branches/cache_aware/deliverance/cache_fixture.py Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: wsgi testing & fixes Added: z3/deliverance/branches/cache_aware/deliverance/cache_fixture.py ============================================================================== --- (empty file) +++ z3/deliverance/branches/cache_aware/deliverance/cache_fixture.py Fri Feb 2 20:17:28 2007 @@ -0,0 +1,78 @@ + +from paste.response import replace_header +from paste.httpheaders import IF_MODIFIED_SINCE, EXPIRES, CONTENT_LENGTH, ETAG + +class CacheFixtureResponseInfo(object): + def __init__(self, data, mod_time=None, + etag=None, cache_control=None, + expires=None): + self.data = data + self.mod_time = mod_time + self.etag = etag + self.cache_control = cache_control + self.expires = expires + +class CacheFixtureApp(object): + """ + a crumby app that can be set up with + dummy content for different urls and + be configured with a variety of responses when + cache related headers are present in the request. + + responds with a 404 for any url not explicitly + mapped in. + """ + def __init__(self): + self.responses = {} + + def map_url(self, path, response_info): + self.responses[path] = response_info + + def get_response_info(self, path): + return self.responses.get(path, None) + + def __call__(self, environ, start_response): + path = environ['PATH_INFO'] + + if path in self.responses: + response_info = self.responses[path] + + headers = self.calc_headers(response_info) + + if response_info.mod_time is not None and 'HTTP_IF_MODIFIED_SINCE' in environ: + req_time = IF_MODIFIED_SINCE.parse(environ['HTTP_IF_MODIFIED_SINCE']) + + if req_time > response_info.mod_time: + replace_header(headers, 'content-length', '0') + start_response('304 Not Modified', headers) + return [] + + if response_info.etag is not None and 'HTTP_IF_NONE_MATCH' in environ: + # XXX this expects only one etag, but it could be more than one + req_etag = environ['HTTP_IF_NONE_MATCH'] + if response_info.etag == req_etag: + replace_header(headers, 'content-length', '0') + start_response('304 Not Modified', headers) + return [] + + headers.append(('content-length', str(len(response_info.data)))) + headers.append(('content-type', 'text/html')) + + start_response('200 OK', headers) + return [response_info.data] + + else: + start_response('404 Not Found', [('content-length','0')]) + return [] + + def calc_headers(self, response_info): + headers = [] + + if response_info.etag is not None: + replace_header(headers, 'etag', response_info.etag) + if response_info.cache_control is not None: + headers.add(('cache-control', response_info.cache_control)) + if response_info.expires is not None: + EXPIRES.update(headers,'expires', time=response_info.expires) + + return headers Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/cache_utils.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Fri Feb 2 20:17:28 2007 @@ -9,11 +9,11 @@ utilities for fusing cache related HTTP headers from multiple sources -XXX there is probably a good amount of work in here -that Paste could simplify +XXX +there is probably a good amount of work in here that Paste could simplify +tests that depend on set ordering TODO: -handle expires handle last-modified """ @@ -471,6 +471,8 @@ newcc[directive] = str(min) + + def flatten_directive_map(d): """ flattens a map of directive -> fieldnames Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Fri Feb 2 20:17:28 2007 @@ -49,17 +49,13 @@ self.environ['HTTP_ACCEPT_ENCODING'] = '' def wsgi_get(self): - print "Internal Resource get: %s" % self.uri if 'paste.recursive.include' in self.environ: - print "Doing paste.recursive.include" # Try to do the redirect this way... includer = self.environ['paste.recursive.include'] res = includer(self.uri, self.environ) return (res.status, res.headers, res.body) else: - print "Doing intercept" status, headers, body = intercept_output(self.environ, self.app) - print " => %s" % status return (status, headers, body) @@ -115,7 +111,6 @@ # self.environ['SERVER_PORT'] = '80' def wsgi_get(self): - print "External Resource get: %s" % self.uri proxy_app = TransparentProxy() return intercept_output(self.environ, proxy_app) Modified: z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py Fri Feb 2 20:17:28 2007 @@ -3,9 +3,14 @@ from lxml import etree from paste.fixture import TestApp from paste.urlparser import StaticURLParser +from paste.response import header_value from deliverance.wsgimiddleware import DeliveranceMiddleware from formencode.doctest_xml_compare import xml_compare from deliverance.htmlserialize import tostring +from deliverance.cache_fixture import CacheFixtureResponseInfo, CacheFixtureApp +from deliverance import cache_utils +from time import time as now +from rfc822 import formatdate static_data = os.path.join(os.path.dirname(__file__), 'test-data', 'static') tasktracker_data = os.path.join(os.path.dirname(__file__), 'test-data', 'tasktracker') @@ -25,6 +30,8 @@ url_app = StaticURLParser(url_data) aggregate_app = StaticURLParser(aggregate_data) + + def html_string_compare(astr, bstr): """ compare to strings containing html based on html @@ -148,8 +155,118 @@ res2 = app.get('/expected.html?notheme') html_string_compare(res.body, res2.body) +def do_cache(renderer_type, name): + theme_data = """ + + theme +
+ + """ + rule_data = """ + + + + """ + + content_data = """ +
foo
+ """ + + expected_data = """ + + + + theme +
foo
+ + """ + + theme_info = CacheFixtureResponseInfo(theme_data) + rule_info = CacheFixtureResponseInfo(rule_data) + content_info = CacheFixtureResponseInfo(content_data) + expected_info = CacheFixtureResponseInfo(expected_data) + + capp = CacheFixtureApp() + capp.map_url('/theme.html',theme_info) + capp.map_url('/rules.xml',rule_info) + capp.map_url('/content.html',content_info) + capp.map_url('/expected.html',expected_info) + + wsgi_app = DeliveranceMiddleware(capp, '/theme.html', '/rules.xml', + renderer_type) + + # check that everything works straight up + app = TestApp(wsgi_app) + res = app.get('/content.html') + res2 = app.get('/expected.html?notheme') + html_string_compare(res.body, res2.body) + + # set some etags on the fixture + theme_info.etag = "theme_etag" + rule_info.etag = "rule_etag" + content_info.etag = "content_etag" + + + # grab the page and make sure an etag comes back + res = app.get('/content.html') + composite_etag = header_value(res.headers, 'etag') + assert(composite_etag is not None and len(composite_etag) > 0) + + # check that deliverance gives 304 when the composite etag is given + res = app.get('/content.html', headers={'If-None-Match': composite_etag}) + status = res.status + assert(status == 304) + + theme_info.etag = 'something_else' + # check that deliverance rebuilds when one of the etags changes + res = app.get('/content.html', headers={'If-None-Match': composite_etag}) + status = res.status + # make sure the response etag changed + assert(header_value(res.headers, 'etag') != composite_etag) + assert(status == 200) + + # clear etags + theme_info.etag = None + rule_info.etag = None + content_info.etag = None + + # make sure there is no more etag + res = app.get('/content.html') + composite_etag = header_value(res.headers, 'etag') + assert(composite_etag is None or len(composite_etag) == 0) + + # test modification dates + then = now() + theme_info.mod_time = then - 10 + rule_info.mod_time = then - 20 + content_info.mod_time = then - 30 + + res = app.get('/content.html') + status = res.status + assert(status == 200) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then)}) + status = res.status + assert(status == 304) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-60)}) + status = res.status + assert(status == 200) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-15)}) + status = res.status + assert(status == 200) + + + + + + RENDERER_TYPES = ['py', 'xslt'] -TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate ] +TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_cache ] def test_all(): for renderer_type in RENDERER_TYPES: for test_func in TEST_FUNCS: Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Fri Feb 2 20:17:28 2007 @@ -50,10 +50,6 @@ self.app = app self.theme_uri = theme_uri self.rule_uri = rule_uri - self._renderer = None - self._cache_time = datetime.datetime.now() - self._timeout = datetime.timedelta(0,10) - self._lock = threading.Lock() if renderer == 'py': import interpreter @@ -67,18 +63,7 @@ self._rendererType = renderer def get_renderer(self, environ): - """ - retrieve the deliverance Renderer representing the transformation this - middlware represents. Renderer may change according to caching rules. - """ - try: - self._lock.acquire() - if not self._renderer or self.cache_expired(): - self._renderer = self.create_renderer(environ) - self._cache_time = datetime.datetime.now() - return self._renderer - finally: - self._lock.release() + return self.create_renderer(environ) def create_renderer(self, environ): """ @@ -127,13 +112,6 @@ rule_uri=self.rule_uri, reference_resolver=reference_resolver) - - def cache_expired(self): - """ - returns true if the stored Renderer should be refreshed - """ - return self._cache_time + self._timeout < datetime.datetime.now() - def rule(self, environ): """ retrieves the data referred to by the rule_uri passed to the @@ -193,8 +171,6 @@ return body # perform actual themeing - print "Doing themeing" - body = self.filter_body(environ, body) replace_header(headers, 'content-length', str(len(body))) @@ -240,11 +216,15 @@ def rebuild_check(self, environ, start_response): - print "===== rebuild check =====" # perform the request for content content_url = construct_url(environ) + etag_map = {} + if 'HTTP_IF_NONE_MATCH' in environ: + etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + environ['HTTP_IF_NONE_MATCH'] = etag_map.get(content_url,None) + status, headers, body = intercept_output(environ, self.app, self.should_intercept, start_response) @@ -252,11 +232,9 @@ if status is None: # should_intercept says this isn't HTML, we're done - print "ignore non-html: %s" % construct_url(environ) return (None, None, body) if self.should_ignore_url(content_url): - print "ignore blacklisted url: %s" % construct_url(environ) start_response(status, headers) return (None, None, [body]) @@ -265,11 +243,8 @@ # it was modified or an error, give it back for themeing if not status.startswith('304'): - print "Content %s modified, continue..." % content_url - # if it's not a full HTML page, skip it if not self.hasHTMLTag(body): - print "ignore non-html-tagged: %s" % construct_url(environ) start_response(status, headers) return (None, None, [body]) @@ -278,11 +253,10 @@ # got 304 Not Modified for content, check other resources rules = etree.XML(self.rule(environ)) - resources = self.get_resource_uris(rules) - if self.any_modified(environ, resources): + resources = self.get_resource_uris(rules) + if self.any_modified(environ, resources, etag_map): # something changed, # get the content explicitly and give it back - print "explicitly requesting %s" % construct_url(environ) if 'HTTP_IF_MODIFIED_SINCE' in environ: environ['HTTP_IF_MODIFIED_SINCE'] = '' if 'HTTP_IF_NONE_MATCH' in environ: @@ -293,8 +267,6 @@ if not self.hasHTMLTag(body): # XXX yarg, we didn't care about it! - print "ARG ignore non-html: status: %s, %s" % (status, construct_url(environ)) - #print "Environ: " , environ , " Headers: ", headers start_response(status, headers) return (None, None, [body]) @@ -302,7 +274,6 @@ return (status, headers, body) # nothing was modified, give back a 304 - print "giving back 304: %s" % construct_url(environ) cache_utils.merge_cache_headers(environ, environ[DELIVERANCE_CACHE], headers) @@ -310,7 +281,7 @@ return (None,None,[]) - def any_modified(self, environ, resources): + def any_modified(self, environ, resources, etag_map): """ returns a tuple containing a boolean and map of uris to HTTP response headers. the first value represents whether any resource in resources has been @@ -319,16 +290,10 @@ second element of the tuple. """ - print "====== rebuild check ======" moddate = None - etag_map = {} if 'HTTP_IF_MODIFIED_SINCE' in environ: - print "using modification date: %s" % environ['HTTP_IF_MODIFIED_SINCE'] moddate = environ['HTTP_IF_MODIFIED_SINCE'] - if 'HTTP_IF_NONE_MATCH' in environ: - print "using composite etag: %s" % environ['HTTP_IF_NONE_MATCH'] - etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) for uri in resources: if (self.check_modification(environ, uri, @@ -348,10 +313,8 @@ if uri in environ[DELIVERANCE_CACHE]: response = environ[DELIVERANCE_CACHE][uri] if response[0].startswith('200'): - print "using previously fetched content for %s" % uri return response[2] - print "fetching resource from scratch: %s" % uri fetcher = self.get_fetcher(environ, uri) # eliminate validation headers, we want the content @@ -418,7 +381,6 @@ """ - print "[!] Checking modification for: [%s] w/ [%s,%s]" % (uri, httpdate_since, etag) fetcher = self.get_fetcher(environ, uri) @@ -437,10 +399,6 @@ status, headers, body = fetcher.wsgi_get() environ[DELIVERANCE_CACHE][uri] = (status, headers, body) - print "status was: [%s]" % status - if not (status.startswith('200') or status.startswith('304')): - print "status(%s), environ => %s, headers => %s" % (status, fetcher.environ, headers) - if status.startswith('304'): # Not Modified return False From ltucker at codespeak.net Fri Feb 2 20:48:32 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 2 Feb 2007 20:48:32 +0100 (CET) Subject: [z3-checkins] r37820 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070202194832.21DB510063@code0.codespeak.net> Author: ltucker Date: Fri Feb 2 20:48:20 2007 New Revision: 37820 Modified: z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py Log: more wsgi caching tests Modified: z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py Fri Feb 2 20:48:20 2007 @@ -156,6 +156,8 @@ html_string_compare(res.body, res2.body) def do_cache(renderer_type, name): + # XXX this should be busted up into multiple tests I spose + theme_data = """ theme @@ -260,9 +262,54 @@ status = res.status assert(status == 200) + # test a mixture + theme_info.etag = None + rule_info.etag = 'joop' + content_info.etag = 'win' + theme_info.mod_time = then - 20 + rule_info.mod_time = None + content_info.mod_time = None + + # get the new etag, make sure things are ok for non-cachey req + res = app.get('/content.html') + composite_etag = header_value(res.headers, 'etag') + assert(composite_etag is not None and len(composite_etag) > 0) + assert(res.status == 200) + + # should be not modified with the etags and a date after the mod date + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-15), + 'If-None-Match': composite_etag}) + status = res.status + assert(status == 304) + + # should be modified if the date is after the mod date since there is + # no etag for theme + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-25), + 'If-None-Match': composite_etag}) + status = res.status + assert(status == 200) + + # should be modified since there is no etag specified and no + # moddate for rules / content + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-15)}) + status = res.status + assert(status == 200) + + # should be modified since there is no etag for theme + res = app.get('/content.html', + headers={'If-None-Match': composite_etag}) + status = res.status + assert(status == 200) + + + + RENDERER_TYPES = ['py', 'xslt'] From ianb at codespeak.net Sun Feb 4 04:30:02 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Sun, 4 Feb 2007 04:30:02 +0100 (CET) Subject: [z3-checkins] r37888 - z3/deliverance/trunk/deliverance Message-ID: <20070204033002.C231E1006E@code0.codespeak.net> Author: ianb Date: Sun Feb 4 04:29:57 2007 New Revision: 37888 Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py Log: Fix up the use of DELIVERANCE_BASE_URL in remote calls; don't swallow at least a couple exceptions. Use the (not-yet-implemented in Paste) public_html attribute to show some information about the failure. Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Sun Feb 4 04:29:57 2007 @@ -107,10 +107,8 @@ try: parsedRule = etree.XML(rule) except Exception, message: - newmessage = "Unable to parse rules (" + self.rule_uri + ")" - if message: - newmessage += ":" + str(message) - raise DeliveranceError(newmessage) + message.public_html = 'Cannot parse rules (%s)' % message + raise return self._rendererType( theme=parsedTheme, @@ -132,7 +130,7 @@ initializer. """ try: - return self.get_resource(environ,self.rule_uri) + return self.get_resource(environ, self.rule_uri) except Exception, message: newmessage = "Unable to retrieve rules from " + self.rule_uri if message: @@ -148,11 +146,9 @@ try: return self.get_resource(environ,self.theme_uri) except Exception, message: - newmessage = "Unable to retrieve theme page from " + self.theme_uri - if message: - newmessage += ": " + str(message) - raise DeliveranceError(newmessage) - + message.public_html = 'Unable to retrieve theme page from %s: %s' % ( + self.theme_uri, message) + raise def __call__(self, environ, start_response): """ @@ -226,7 +222,7 @@ """ internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) uri = urlparse.urljoin(internalBaseURL, uri) - + if internalBaseURL and uri.startswith(internalBaseURL): return self.get_internal_resource(environ, uri[len(internalBaseURL):]) else: @@ -257,7 +253,6 @@ get the data referred to by the uri given by using the wrapped WSGI application """ - if 'paste.recursive.include' in in_environ: environ = in_environ['paste.recursive.include'].original_environ.copy() @@ -267,7 +262,9 @@ if not uri.startswith('/'): uri = '/' + uri environ['PATH_INFO'] = uri - environ['SCRIPT_NAME'] = in_environ[DELIVERANCE_BASE_URL] + base = in_environ[DELIVERANCE_BASE_URL] + scheme, netloc, path, qs, fragment = urlparse.urlsplit(base) + environ['SCRIPT_NAME'] = path environ['REQUEST_METHOD'] = 'GET' environ['CONTENT_LENGTH'] = '0' environ['wsgi.input'] = StringIO('') @@ -283,7 +280,7 @@ if 'paste.recursive.include' in in_environ: # Try to do the redirect this way... includer = in_environ['paste.recursive.include'] - res = includer(uri,environ) + res = includer(uri, environ) return res.body From ianb at codespeak.net Sun Feb 4 04:30:19 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Sun, 4 Feb 2007 04:30:19 +0100 (CET) Subject: [z3-checkins] r37889 - in z3/deliverance/DeliveranceVHoster/trunk: dvhoster tests Message-ID: <20070204033019.EAE5910072@code0.codespeak.net> Author: ianb Date: Sun Feb 4 04:30:14 2007 New Revision: 37889 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Log: Make the functional tests pass, w00t Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Sun Feb 4 04:30:14 2007 @@ -51,20 +51,93 @@ print 'Creating data directory %s' % self.dir os.makedirs(self.dir) - def domain(self, domain_name): + def alias_fn(self, alias): + return os.path.join(self.dir, alias+'-alias.txt') + + def domain(self, domain_name, aliases=()): + """ + Return the domain object for the given domain_name. The + domain name given may not be the canonical domain. + """ domain_name = self.normalize(domain_name) + alias_fn = self.alias_fn(domain_name) + if os.path.exists(alias_fn): + f = open(alias_fn, 'rb') + alias = f.read() + f.close() + aliases = list(aliases) + if alias in aliases: + raise ValueError( + "Infinite alias loop (found: %s -> %s)" + % (aliases, alias)) + aliases.append(alias) + return self.domain(alias, aliases=aliases) dir = os.path.join(self.dir, domain_name) if not os.path.exists(dir): os.mkdir(dir) return DomainDataProvider(self, domain_name, dir) def normalize(self, domain_name): + """ + Normalize a domain name (lower case, no funny characters). + """ domain_name = domain_name.strip().lower() if not domain_re.search(domain_name): raise ValueError( 'Bad domain name: %r' % domain_name) return domain_name + def add_alias(self, alias, domain): + """ + Add an alias domain ``alias`` which points to ``domain``. + There must not be an existing alias. + """ + alias = self.normalize(alias) + domain = self.normalize(domain) + fn = self.alias_fn(alias) + assert not os.path.exists(fn), ( + "Alias file already exists (%r)" % fn) + f = open(fn, 'wb') + f.write(domain) + f.close() + + def remove_alias(self, alias, domain=None): + """ + Remove the alias for the domain ``alias``. ``domain`` (if + given) is what the alias should currently be pointing to (we + won't remove the alias then if it points elsewhere). + """ + alias = self.normalize(alias) + if domain: + domain = self.normalize(domain) + fn = self.alias_fn(alias) + if not os.path.exists(fn): + raise ValueError( + "Alias does not exist (not file %r)" % fn) + if domain: + f = open(fn, 'rb') + existing = f.read().strip() + f.close() + if existing != domain: + raise ValueError( + "Tried to remove alias from %s->%s, but %s actually " + "points to %s" % (alias, domain, alias, existing)) + os.unlink(fn) + + def rename_domain(self, old_domain_name, new_domain_name): + """ + Rename the old domain to the new domain, returning the new domain + object. + """ + old_domain_name = self.normalize(old_domain_name) + new_domain_name = self.normalize(new_domain_name) + old_dir = os.path.join(self.dir, old_domain_name) + new_dir = os.path.join(self.dir, new_domain_name) + assert os.path.exists(old_dir) + assert not os.path.exists(new_dir) + os.rename(old_dir, new_dir) + return self.domain(new_domain_name) + class DomainDataProvider(object): def __init__(self, provider, domain_name, base_dir): @@ -95,7 +168,7 @@ @property def initialized(self): - return hasattr(self, 'remote') + return hasattr(self, 'remote_uris') @property def rule_dir(self): @@ -105,11 +178,29 @@ def static_dir(self): return os.path.join(self.base_dir, 'static') - remote = persist.file_property('remote.txt') + remote_uris = descriptors.json_converter( + persist.file_property('remote_uris.txt')) theme_uri = persist.file_property('theme_uri.txt') theme_id = persist.file_property('theme_id.txt') rule_ids = persist.file_property('rule_ids.txt') - aliases = descriptors.line_converter(persist.file_property('aliases.txt')) + redirects = descriptors.json_converter( + persist.file_property('redirects.txt', default=())) + + def aliases__rename(self, old_aliases, new_aliases): + dropped = list(old_aliases) + added = list(new_aliases) + for new_alias in new_aliases: + if new_alias in dropped: + dropped.remove(new_alias) + added.remove(new_alias) + for alias in dropped: + self.provider.remove_alias(alias, self.domain) + for alias in added: + self.provider.add_alias(alias, self.domain) + + aliases = descriptors.watcher( + descriptors.line_converter(persist.file_property('aliases.txt', default='')), + after_watcher=aliases__rename) def set_rule_file(self, filename, content): filename = os.path.join(self.rule_dir, filename) @@ -119,11 +210,16 @@ def domain__get(self): return self._domain_name - - def domain__set(self, value): - new_obj = self.provider.rename_domain(self.domain, value) + + def domain__set(self, new_domain): + aliases = self.aliases + old_domain = self.domain + new_obj = self.provider.rename_domain(self.domain, new_domain) # Clone new object as self: self.__dict__.update(new_obj.__dict__) + for alias in aliases: + self.provider.remove_alias(alias, domain=old_domain) + self.provider.add_alias(alias, new_domain) domain = property(domain__get, domain__set) @@ -133,6 +229,7 @@ class ListDictValidator(validators.FancyValidator): required_keys = () optional_keys = () + trail_slash_keys = () def validate_python(self, value, state=None): # Should be like [{'path': path, 'remote_uri': uri, 'comment': str}] @@ -158,14 +255,25 @@ except AssertionError, e: raise validators.Invalid(str(e), value, state) + def _to_python(self, value, state=None): + for d in value: + for key in self.trail_slash_keys: + if key not in d: + continue + if not d[key].endswith('/'): + d[key] += '/' + return value + class RemoteURIValidator(ListDictValidator): required_keys = ('path', 'remote_uri') optional_keys = ('comment', ) + trail_slash_keys = ('path', 'remote_uri') class RewriteValidator(ListDictValidator): required_keys = ('rewrite', ) # @@: Really we should require one of path or prefix optional_keys = ('path', 'prefix', 'comment') + trail_slash_keys = ('prefix', 'rewrite') class ProviderApp(server.ApplicationWrapper): @@ -190,4 +298,4 @@ def static(self): if not os.path.exists(self.static_dir): os.makedirs(self.static_dir) - return lildav.LilDAV(self.rule_dir) + return lildav.LilDAV(self.static_dir) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py Sun Feb 4 04:30:14 2007 @@ -17,7 +17,17 @@ if not current_environ.get('dvhoster.has_errors'): current_environ['dvhoster.has_errors'] = True domain_info = current_environ['dvhoster.domain_info'] - remote = domain_info.remote + remote_uris = domain_info.remote_uris + path_info = current_environ['PATH_INFO'] + for remote_info in remote_uris: + if (path_info.startswith(remote_info['path']) + or not path_info and remote_info['path'] == '/'): + remote = remote_info['remote_uri'] + break + else: + assert 0, ( + "Nothing in remote_uris (%r) matches PATH_INFO=%r" + % (remote_uris, path_info)) remote += current_environ.get('PATH_INFO', '') if current_environ.get('QUERY_STRING'): remote += '?' + current_environ['QUERY_STRING'] Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Sun Feb 4 04:30:14 2007 @@ -1,5 +1,6 @@ import posixpath import os +import urlparse from paste.request import path_info_pop, construct_url from paste import httpexceptions from paste.urlparser import StaticURLParser @@ -32,6 +33,7 @@ domain = environ['HTTP_HOST'] if ':' in domain: domain = domain.split(':', 1)[0] + domain = self.provider.normalize(domain) domain_info = self.provider.domain(domain) environ['dvhoster.domain_info'] = domain_info environ['dvhoster.base_url'] = construct_url( @@ -42,12 +44,27 @@ path_info_pop(environ) subapp = ProviderApp(domain_info) return subapp(environ, start_response) + + if domain_info.domain != domain: + # We got an alias + assert domain in domain_info.aliases, ( + "Domain %r not found in aliases %r" + % (domain, domain_info.aliases)) + new_environ = environ.copy() + new_environ['HTTP_HOST'] = domain_info.domain + new_url = construct_url(new_environ) + exc = httpexceptions.HTTPMovedPermanently( + headers=[('Location', new_url)], + comment='Redirecting to canonical domain') + return exc(environ, start_response) + if path_info.startswith('/_rules'): path_info_pop(environ) + environ['QUERY_STRING'] = '' subapp = StaticURLParser( domain_info.rule_dir) return subapp(environ, start_response) - + static_path = os.path.join(domain_info.static_dir, path_info.lstrip('/')) static_fn = self.find_file(static_path) @@ -55,12 +72,69 @@ # Explicit override of a file app = FileApp(static_fn) return app(environ, start_response) + try: + remote_uris = domain_info.remote_uris + except AttributeError: + exc = httpexceptions.HTTPNotFound( + "This domain has not yet been configured (no remote " + "URI has been configured for %s)" + % domain_info.domain) + return exc(environ, start_response) + remote_uri = None + current_uri = construct_url(environ) + for redirect_info in domain_info.redirects: + if 'path' in redirect_info: + if redirect_info['path'] == path_info: + new = redirect_info['rewrite'] + new = urlparse.urljoin(current_uri, redirect_info['rewrite']) + exc = httpexceptions.HTTPMovedPermanently( + headers=[('Location', new)]) + return exc(environ, start_response) + elif 'prefix' in redirect_info: + assert redirect_info['prefix'].endswith('/') + new_uri = None + if path_info == redirect_info['prefix'][:-1]: + new_uri = redirect_info['rewrite'] + '/' + elif path_info.startswith(redirect_info['prefix']): + new_uri = redirect_info['rewrite'] + if not new_uri.endswith('/'): + new_uri += '/' + new_uri += path_info[len(redirect_info['prefix']):] + if new_uri is not None: + new_uri = urlparse.urljoin(current_uri, new_uri) + exc = httpexceptions.HTTPMovedPermanently( + headers=[('Location', new_uri)]) + return exc(environ, start_response) + + for remote_uri_info in remote_uris: + path = remote_uri_info['path'] + if not path.endswith('/'): + path += '/' + if path_info + '/' == path: + exc = httpexceptions.HTTPMovedPermanently( + headers=[('location', construct_url(environ, path_info=path_info+'/'))]) + return exc(environ, start_response) + if path_info.startswith(path): + # Found a match + remote_uri = remote_uri_info['remote_uri'] + environ['SCRIPT_NAME'] += path[:-1] + environ['PATH_INFO'] = path_info[len(path)-1:] + break + if not remote_uri: + exc = httpexceptions.HTTPNotFound( + "No URL is mapped to %r (somewhat oddly); only %s prefixes " + "are available" + % (path_info, ', '.join([repr(r['path']) + for r in remote_uris]))) + return exc(environ, start_response) + print 'Proxy to %r with %r (SCRIPT_NAME=%r)' % ( + remote_uri, construct_url(environ), environ['SCRIPT_NAME']) app = proxyapp.ForcedProxy( - remote=domain_info.remote, + remote=remote_uri, force_host=True) if self.rewrite_links: app = relocateresponse.RelocateMiddleware( - app, old_href=domain_info.remote) + app, old_href=remote_uri) rule_uri = construct_url( environ, with_query_string=False, path_info='/_rules/rule.xml') Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/wsgiapp.py Sun Feb 4 04:30:14 2007 @@ -3,6 +3,7 @@ #from paste.deploy.config import ConfigMiddleware from paste.deploy.converters import asbool from paste.recursive import RecursiveMiddleware +from paste.registry import RegistryManager from wsgifilter import proxyapp from paste.exceptions.errormiddleware import ErrorMiddleware @@ -12,6 +13,7 @@ """Create a WSGI application and return it""" app = DeliveranceDispatcher(app_conf) app = RecursiveMiddleware(app) + app = RegistryManager(app) debug = app_conf['debug'] = asbool(app_conf.get('debug', global_conf.get('debug'))) if asbool(app_conf.get('debug_headers')): app = proxyapp.DebugHeaders(app, show_body=asbool(app_conf.get('debug_bodies'))) Modified: z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Sun Feb 4 04:30:14 2007 @@ -4,15 +4,21 @@ from paste.urlparser import StaticURLParser from dvhoster.wsgiapp import make_app import wsgi_intercept +import httplib +for attr in ['get_app', 'connect']: + setattr(httplib.HTTPConnection, attr, + getattr(wsgi_intercept.WSGI_HTTPConnection, attr).im_func) data_filename = os.path.join(os.path.dirname(__file__), 'test-data') wsgi_app = make_app({}, data_dir=data_filename) app = TestApp(wsgi_app) def put(uri, data): - app.post(uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}, status=(201, 204)) + return app.post( + uri, data, extra_environ={'REQUEST_METHOD': 'PUT'}, + status=(201, 204)) -rule_data = ''' +rule_data = '''\ @@ -53,7 +59,9 @@ assert res.body == uri # Rules: - put('/.deliverance/rules/rules.xml', rule_data) + put('/.deliverance/rules/rule.xml', rule_data) + assert app.get('/.deliverance/rules/rule.xml').body == rule_data + assert app.get('/_rules/rule.xml').body == rule_data # Domain (no rename test): res = app.get('/.deliverance/domain') @@ -65,11 +73,12 @@ assert app.get('/.deliverance/aliases').body == data data = ''' - [{"path": "/", "remote_uri": "http://wsgify.org/"}, - {"path": "/bar", "remote_uri": "http://wsgify.org/blah", "comment": "x"}] + [{"path": "/bar", "remote_uri": "http://wsgify.org/blah", "comment": "x"}, + {"path": "/", "remote_uri": "http://wsgify.org/"}] ''' put('/.deliverance/remote_uris', data) + # It gets normalized, so it doesn't actually stay the same: #assert app.get('/.deliverance/remote_uris').body == data data = ''' @@ -85,7 +94,7 @@ assert res.body == data app.get('/.deliverance/static/subdir', extra_environ={'REQUEST_METHOD': 'MKCOL'}, status=201) - put('/.deliverance/static/subdir/foo.html', 'blah') + res = put('/.deliverance/static/subdir/foo.html', 'blah') res = app.get('/.deliverance/static/subdir/foo.html') assert res.body == 'blah' res = app.get('/subdir/foo.html') @@ -106,14 +115,14 @@ assert 'unthemed' not in res # Redirect non-canonical domains: res = app.get('/index.html', extra_environ=dict(HTTP_HOST='example.com'), - status=303) + status=301) assert res.header('location') == 'http://%s/index.html' % hostname # Test the path backend redirect: res = app.get('/bar/foo.html') res.mustcontain('foo') - res = app.get('/test1.html', status=303) - assert res.header('location') == 'http://%s/test1' % hostname - res = app.get('/test2/other/stuff.html', status=303) + res = app.get('/test1.html', status=301) + assert res.header('location') == 'http://%s/test1/' % hostname + res = app.get('/test2/other/stuff.html', status=301) assert res.header('location') == 'http://%s/test3/other/stuff.html' % hostname res = app.get('/test1.html/foo/bar', status=404) res = app.get('/data.html') @@ -128,4 +137,4 @@ res = app.get('/.deliverance/aliases') res.mustcontain('localhost') assert app.get('/.deliverance/domain').body == 'localhost2' - + print 'site renamed to localhost2' From ltucker at codespeak.net Mon Feb 5 17:31:25 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Mon, 5 Feb 2007 17:31:25 +0100 (CET) Subject: [z3-checkins] r37974 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070205163125.C030A10077@code0.codespeak.net> Author: ltucker Date: Mon Feb 5 17:31:23 2007 New Revision: 37974 Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Log: fix recursive internal includes Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Mon Feb 5 17:31:23 2007 @@ -15,6 +15,7 @@ if 'paste.recursive.include' in in_environ: self.environ = in_environ['paste.recursive.include'].original_environ.copy() + self.environ['paste.recursive.include'] = in_environ['paste.recursive.include'] else: self.environ = in_environ.copy() From ltucker at codespeak.net Wed Feb 7 00:30:19 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 00:30:19 +0100 (CET) Subject: [z3-checkins] r38041 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070206233019.6C56A10080@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 00:30:17 2007 New Revision: 38041 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: remove deliverance error page, propagate all exceptions Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 00:30:17 2007 @@ -148,53 +148,40 @@ using the transformation specified in the initializer. """ - try: - qs = environ.get('QUERY_STRING', '') - environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) - environ[DELIVERANCE_CACHE] = {} - notheme = 'notheme' in qs - if notheme: - return self.app(environ, start_response) - - # unsupported - if 'HTTP_ACCEPT_ENCODING' in environ: - environ['HTTP_ACCEPT_ENCODING'] = '' - if 'HTTP_IF_MATCH' in environ: - environ['HTTP_IF_MATCH'] = '' - if 'HTTP_IF_UNMODIFIED_SINCE' in environ: - environ['HTTP_IF_UNMODIFIED_SINCE'] = '' + qs = environ.get('QUERY_STRING', '') + environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) + environ[DELIVERANCE_CACHE] = {} + notheme = 'notheme' in qs + if notheme: + return self.app(environ, start_response) + + # unsupported + if 'HTTP_ACCEPT_ENCODING' in environ: + environ['HTTP_ACCEPT_ENCODING'] = '' + if 'HTTP_IF_MATCH' in environ: + environ['HTTP_IF_MATCH'] = '' + if 'HTTP_IF_UNMODIFIED_SINCE' in environ: + environ['HTTP_IF_UNMODIFIED_SINCE'] = '' - status, headers, body = self.rebuild_check(environ, start_response) + status, headers, body = self.rebuild_check(environ, start_response) - # non-html responses, or rebuild is not necessary: bail out - if status is None: - return body + # non-html responses, or rebuild is not necessary: bail out + if status is None: + return body - # perform actual themeing - body = self.filter_body(environ, body) + # perform actual themeing + body = self.filter_body(environ, body) - replace_header(headers, 'content-length', str(len(body))) - replace_header(headers, 'content-type', 'text/html; charset=utf-8') + replace_header(headers, 'content-length', str(len(body))) + replace_header(headers, 'content-type', 'text/html; charset=utf-8') - cache_utils.merge_cache_headers(environ, - environ[DELIVERANCE_CACHE], - headers) + cache_utils.merge_cache_headers(environ, + environ[DELIVERANCE_CACHE], + headers) - start_response(status, headers) - return [body] + start_response(status, headers) + return [body] - except DeliveranceError, message: - stack = StringIO() - traceback.print_exception(sys.exc_info()[0], - sys.exc_info()[1], - sys.exc_info()[2], - file=stack) - status = "500 Internal Server Error" - headers = [('Content-type','text/html')] - start_response(status,headers) - errpage = DELIVERANCE_ERROR_PAGE % (message,stack.getvalue()) - return [ errpage ] - def should_intercept(self, status, headers): """ returns true if the status and headers given From ltucker at codespeak.net Wed Feb 7 18:58:49 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 18:58:49 +0100 (CET) Subject: [z3-checkins] r38084 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207175849.880F810084@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 18:58:45 2007 New Revision: 38084 Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: adds last-modified handling, corrects elimination of validation headers during subrequests, makes cache-control header merging optional and off by default Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/cache_utils.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Wed Feb 7 18:58:45 2007 @@ -1,6 +1,6 @@ import re from paste.response import header_value, replace_header -from paste.httpheaders import EXPIRES +from paste.httpheaders import EXPIRES, LAST_MODIFIED from time import time as now from sets import Set @@ -13,30 +13,31 @@ there is probably a good amount of work in here that Paste could simplify tests that depend on set ordering -TODO: -handle last-modified """ -def merge_cache_headers(self, response_info, new_headers): +def merge_cache_headers(self, response_info, new_headers, merge_cache_control=True): """ replaces cache related headers in new_headers with caching info calculated cache_info (a map of urls to wsgi response triples) + if merge_cache_control is False, the cache-control header is + not calculated and only etags, last-modified and vary headers are merged. """ headers_map = {} for uri, response in response_info.items(): headers_map[uri] = response[1] - cache_control_map = merge_cache_control(headers_map.values(), upgrade_expires=True) - if len(cache_control_map): - replace_header(new_headers, 'cache-control', - flatten_directive_map(cache_control_map)) - # provide an Expires header if there is a cache-control max-age - if 'max-age' in cache_control_map: - expire_delta = int(new_cache_ctl['max-age']) - EXPIRES.update(new_headers, delta=expire_delta) + if merge_cache_control: + cache_control_map = merge_cache_control(headers_map.values(), upgrade_expires=True) + if len(cache_control_map): + replace_header(new_headers, 'cache-control', + flatten_directive_map(cache_control_map)) + # provide an Expires header if there is a cache-control max-age + if 'max-age' in cache_control_map: + expire_delta = int(new_cache_ctl['max-age']) + EXPIRES.update(new_headers, delta=expire_delta) etag = merge_etags_from_headers(headers_map) if etag is not None: @@ -45,6 +46,10 @@ vary = merge_vary_from_headers(headers_map) if vary is not None: replace_header(new_headers, 'vary', vary) + + last_mod = merge_last_modified_from_headers(headers_map) + if last_mod is not None: + replace_header(new_headers, 'last-modified', last_mod) @@ -112,6 +117,30 @@ return new_cache_ctl + +def merge_last_modified_from_headers(headers_map): + """ + accepts a map from uris to wsgi-style header lists + returns the value for the last-modified header + representing the latest modification date present + in any of the header lists + """ + latest_mod = None + for uri, headers in headers_map.items(): + last_mod = header_value(headers,'last-modified') + if last_mod is not None: + mod_secs = LAST_MODIFIED.parse(last_mod) + if latest_mod is None: + latest_mod = mod_secs + elif mod_secs > latest_mod: + latest_mod = mod_secs + if latest_mod is not None: + tmp = [] + LAST_MODIFIED.update(tmp, time=latest_mod) + return tmp[0][1] + else: + return None + def merge_etags_from_headers(headers_map): """ Modified: z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Wed Feb 7 18:58:45 2007 @@ -53,8 +53,7 @@ if 'paste.recursive.include' in self.environ: # Try to do the redirect this way... includer = self.environ['paste.recursive.include'] - res = includer(self.uri, self.environ) - return (res.status, res.headers, res.body) + return intercept_output(self.environ, includer.application) else: status, headers, body = intercept_output(self.environ, self.app) return (status, headers, body) Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 18:58:45 2007 @@ -1,3 +1,5 @@ + + """ Deliverance theming as WSGI middleware """ @@ -36,7 +38,7 @@ tranformation as a WSGI middleware component. """ - def __init__(self, app, theme_uri, rule_uri, renderer='py'): + def __init__(self, app, theme_uri, rule_uri, renderer='py', merge_cache_control=False): """ initializer @@ -46,10 +48,15 @@ renderer: selects deliverance render class to utilize when performing transformations, may be 'py' or 'xslt' or a Renderer class + merge_cache_control: if set to True, the cache-control header will + be calculated from the cache-control headers of all component pages + during rendering. If set to False, the requested content's + cache-control headers will be used. (does not affect etag merging) """ self.app = app self.theme_uri = theme_uri self.rule_uri = rule_uri + self.merge_cache_control = merge_cache_control if renderer == 'py': import interpreter @@ -153,15 +160,16 @@ environ[DELIVERANCE_CACHE] = {} notheme = 'notheme' in qs if notheme: + environ['QUERY_STRING'] = '' # XXX return self.app(environ, start_response) # unsupported if 'HTTP_ACCEPT_ENCODING' in environ: environ['HTTP_ACCEPT_ENCODING'] = '' if 'HTTP_IF_MATCH' in environ: - environ['HTTP_IF_MATCH'] = '' + del environ['HTTP_IF_MATCH'] if 'HTTP_IF_UNMODIFIED_SINCE' in environ: - environ['HTTP_IF_UNMODIFIED_SINCE'] = '' + del environ['HTTP_IF_UNMODIFIED_SINCE'] status, headers, body = self.rebuild_check(environ, start_response) @@ -177,7 +185,8 @@ cache_utils.merge_cache_headers(environ, environ[DELIVERANCE_CACHE], - headers) + headers, + self.merge_cache_control) start_response(status, headers) return [body] @@ -210,7 +219,13 @@ etag_map = {} if 'HTTP_IF_NONE_MATCH' in environ: etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) - environ['HTTP_IF_NONE_MATCH'] = etag_map.get(content_url,None) + tag = etag_map.get(content_url, None) + environ['HTTP_IF_NONE_MATCH'] = tag + if tag: + environ['HTTP_IF_NONE_MATCH'] = tag + else: + del environ['HTTP_IF_NONE_MATCH'] + status, headers, body = intercept_output(environ, self.app, self.should_intercept, @@ -245,9 +260,9 @@ # something changed, # get the content explicitly and give it back if 'HTTP_IF_MODIFIED_SINCE' in environ: - environ['HTTP_IF_MODIFIED_SINCE'] = '' + del environ['HTTP_IF_MODIFIED_SINCE'] if 'HTTP_IF_NONE_MATCH' in environ: - environ['HTTP_IF_NONE_MATCH'] = '' + del environ['HTTP_IF_NONE_MATCH'] environ['CACHE-CONTROL'] = 'no-cache' status, headers, body = intercept_output(environ, self.app) @@ -263,7 +278,8 @@ # nothing was modified, give back a 304 cache_utils.merge_cache_headers(environ, environ[DELIVERANCE_CACHE], - headers) + headers, + self.merge_cache_control) start_response('304 Not Modified', headers) return (None,None,[]) @@ -303,14 +319,16 @@ return response[2] fetcher = self.get_fetcher(environ, uri) - + + # eliminate validation headers, we want the content if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: - fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = '' + del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] if 'HTTP_IF_NONE_MATCH' in fetcher.environ: - fetcher.environ['HTTP_IF_NONE_MATCH'] = '' + del fetcher.environ['HTTP_IF_NONE_MATCH'] fetcher.environ['CACHE-CONTROL'] = 'no-cache' + status, headers, body = fetcher.wsgi_get() if not status.startswith('200'): @@ -368,25 +386,24 @@ """ - fetcher = self.get_fetcher(environ, uri) if httpdate_since: fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = httpdate_since else: - fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = '' + del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] if etag: fetcher.environ['HTTP_IF_NONE_MATCH'] = etag else: - fetcher.environ['HTTP_IF_NONE_MATCH'] = '' + del fetcher.environ['HTTP_IF_NONE_MATCH'] status, headers, body = fetcher.wsgi_get() environ[DELIVERANCE_CACHE][uri] = (status, headers, body) - if status.startswith('304'): # Not Modified + if status.startswith('304'): # Not Modified return False return True From ltucker at codespeak.net Wed Feb 7 19:10:36 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 19:10:36 +0100 (CET) Subject: [z3-checkins] r38087 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207181036.607741008A@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 19:10:33 2007 New Revision: 38087 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: just comments Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 19:10:33 2007 @@ -286,11 +286,11 @@ def any_modified(self, environ, resources, etag_map): """ - returns a tuple containing a boolean and map of uris to HTTP response headers. - the first value represents whether any resource in resources has been - modified based on the checks contained in environ. The uris in the list - resources are associated with their respective response headers in the - second element of the tuple. + returns a boolean indicating whether any of the uris in the resources + list have been modified. if an entry for the uri exists in the map + etag_map, the value will be used to check the resource using an + if-none-match http header. if an if-not-modified check is desired, + it should be present in environ. """ moddate = None @@ -349,6 +349,10 @@ def get_fetcher(self, environ, uri): + """ + retrieve an object which is appropriate for fetching the + uri specified. + """ internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) uri = urlparse.urljoin(internalBaseURL, uri) @@ -384,6 +388,9 @@ if etag is set to an etag for the resource, the If-None-Match HTTP header is used to check for modification + the resulting (status, headers, body) tuple for the request is stored in + environ[DELIVERANCE_CACHE][uri]. + """ fetcher = self.get_fetcher(environ, uri) From ltucker at codespeak.net Wed Feb 7 19:14:16 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 19:14:16 +0100 (CET) Subject: [z3-checkins] r38088 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207181416.5FF9E1008E@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 19:14:14 2007 New Revision: 38088 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: only del environ entries if they exist Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 19:14:14 2007 @@ -224,7 +224,8 @@ if tag: environ['HTTP_IF_NONE_MATCH'] = tag else: - del environ['HTTP_IF_NONE_MATCH'] + if 'HTTP_IF_NONE_MATCH' in environ: + del environ['HTTP_IF_NONE_MATCH'] status, headers, body = intercept_output(environ, self.app, @@ -398,13 +399,15 @@ if httpdate_since: fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = httpdate_since else: - del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] + if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: + del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] if etag: fetcher.environ['HTTP_IF_NONE_MATCH'] = etag else: - del fetcher.environ['HTTP_IF_NONE_MATCH'] + if 'HTTP_IF_NONE_MATCH' in fetcher.environ: + del fetcher.environ['HTTP_IF_NONE_MATCH'] status, headers, body = fetcher.wsgi_get() From ltucker at codespeak.net Wed Feb 7 19:18:24 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 19:18:24 +0100 (CET) Subject: [z3-checkins] r38089 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207181824.ECD7C1008E@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 19:18:22 2007 New Revision: 38089 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: eliminate notheme from query when passing to subapp Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 19:18:22 2007 @@ -160,7 +160,11 @@ environ[DELIVERANCE_CACHE] = {} notheme = 'notheme' in qs if notheme: - environ['QUERY_STRING'] = '' # XXX + # eliminate the deliverance notheme query argument for the subrequest + if qs == 'notheme': + environ['QUERY_STRING'] = '' + if qs.endswith('¬heme'): + environ['QUERY_STRING'] = qs[:-len('¬heme')] return self.app(environ, start_response) # unsupported From ltucker at codespeak.net Wed Feb 7 19:19:23 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 19:19:23 +0100 (CET) Subject: [z3-checkins] r38090 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207181923.9A5DC10086@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 19:19:21 2007 New Revision: 38090 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: eliminate needless check Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 19:19:21 2007 @@ -163,7 +163,7 @@ # eliminate the deliverance notheme query argument for the subrequest if qs == 'notheme': environ['QUERY_STRING'] = '' - if qs.endswith('¬heme'): + elif qs.endswith('¬heme'): environ['QUERY_STRING'] = qs[:-len('¬heme')] return self.app(environ, start_response) From ltucker at codespeak.net Wed Feb 7 19:26:32 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 7 Feb 2007 19:26:32 +0100 (CET) Subject: [z3-checkins] r38092 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070207182632.27E1810086@code0.codespeak.net> Author: ltucker Date: Wed Feb 7 19:26:29 2007 New Revision: 38092 Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: more ignorable extensions Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Wed Feb 7 19:26:29 2007 @@ -28,7 +28,8 @@ DELIVERANCE_CACHE = 'deliverance.cache' IGNORE_EXTENSIONS = ['js','css','gif','jpg','jpeg','pdf','ps','doc','png','ico','mov','mpg','mpeg', 'mp3','m4a', - 'txt','rtf'] + 'txt','rtf', 'swf', 'wav', 'zip', 'wmv', 'ppt', 'gz', 'tgz', 'jar', 'xls', 'bmp', 'tif', 'tga', + 'hqx', 'avi'] IGNORE_URL_PATTERN = re.compile("^.*\.(%s)$" % '|'.join(IGNORE_EXTENSIONS)) From ltucker at codespeak.net Thu Feb 8 22:34:21 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Thu, 8 Feb 2007 22:34:21 +0100 (CET) Subject: [z3-checkins] r38210 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070208213421.6A26B10082@code0.codespeak.net> Author: ltucker Date: Thu Feb 8 22:34:18 2007 New Revision: 38210 Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Log: only include etag if all content includes etag Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/cache_utils.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Thu Feb 8 22:34:18 2007 @@ -1,5 +1,5 @@ import re -from paste.response import header_value, replace_header +from paste.response import header_value, replace_header, remove_header from paste.httpheaders import EXPIRES, LAST_MODIFIED from time import time as now from sets import Set @@ -42,6 +42,8 @@ etag = merge_etags_from_headers(headers_map) if etag is not None: replace_header(new_headers, 'etag', etag ) + else: + remove_header(new_headers, 'etag') vary = merge_vary_from_headers(headers_map) if vary is not None: @@ -146,13 +148,19 @@ """ accepts a map from uris to wsgi-style header lists returns the value for the etag merged from all - etag headers present in the header lists + etag headers present in the header lists. + + if any entry in the map does not have an + etag, the resulting etag is None. """ etag_map = {} + for uri, headers in headers_map.items(): etag = header_value(headers,'etag') if etag is not None and len(etag) != 0: etag_map[uri] = etag + else: + return None return merge_etags(etag_map) From ltucker at codespeak.net Fri Feb 9 02:13:16 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 9 Feb 2007 02:13:16 +0100 (CET) Subject: [z3-checkins] r38226 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070209011316.B72691007E@code0.codespeak.net> Author: ltucker Date: Fri Feb 9 02:13:13 2007 New Revision: 38226 Modified: z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py Log: eliminate tests that are made obsolete by etags-only-if-all-have-etags change Modified: z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/test_wsgi.py Fri Feb 9 02:13:13 2007 @@ -262,47 +262,6 @@ status = res.status assert(status == 200) - # test a mixture - theme_info.etag = None - rule_info.etag = 'joop' - content_info.etag = 'win' - theme_info.mod_time = then - 20 - rule_info.mod_time = None - content_info.mod_time = None - - # get the new etag, make sure things are ok for non-cachey req - res = app.get('/content.html') - composite_etag = header_value(res.headers, 'etag') - assert(composite_etag is not None and len(composite_etag) > 0) - assert(res.status == 200) - - # should be not modified with the etags and a date after the mod date - res = app.get('/content.html', - headers={'If-Modified-Since': formatdate(then-15), - 'If-None-Match': composite_etag}) - status = res.status - assert(status == 304) - - # should be modified if the date is after the mod date since there is - # no etag for theme - res = app.get('/content.html', - headers={'If-Modified-Since': formatdate(then-25), - 'If-None-Match': composite_etag}) - status = res.status - assert(status == 200) - - # should be modified since there is no etag specified and no - # moddate for rules / content - res = app.get('/content.html', - headers={'If-Modified-Since': formatdate(then-15)}) - status = res.status - assert(status == 200) - - # should be modified since there is no etag for theme - res = app.get('/content.html', - headers={'If-None-Match': composite_etag}) - status = res.status - assert(status == 200) From ltucker at codespeak.net Fri Feb 9 02:38:16 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 9 Feb 2007 02:38:16 +0100 (CET) Subject: [z3-checkins] r38228 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070209013816.CB73910075@code0.codespeak.net> Author: ltucker Date: Fri Feb 9 02:38:14 2007 New Revision: 38228 Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Log: only compute last-modified if all elements have a last-modified Modified: z3/deliverance/branches/cache_aware/deliverance/cache_utils.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/cache_utils.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/cache_utils.py Fri Feb 9 02:38:14 2007 @@ -52,6 +52,8 @@ last_mod = merge_last_modified_from_headers(headers_map) if last_mod is not None: replace_header(new_headers, 'last-modified', last_mod) + else: + remove_header(new_headers, 'last-modified') @@ -125,7 +127,9 @@ accepts a map from uris to wsgi-style header lists returns the value for the last-modified header representing the latest modification date present - in any of the header lists + in any of the header lists. If any header set does + not specify a last-modified date, the result is + None. """ latest_mod = None for uri, headers in headers_map.items(): @@ -136,6 +140,8 @@ latest_mod = mod_secs elif mod_secs > latest_mod: latest_mod = mod_secs + else: + return None if latest_mod is not None: tmp = [] LAST_MODIFIED.update(tmp, time=latest_mod) From hannosch at codespeak.net Mon Feb 12 10:19:22 2007 From: hannosch at codespeak.net (hannosch at codespeak.net) Date: Mon, 12 Feb 2007 10:19:22 +0100 (CET) Subject: [z3-checkins] r38535 - z3/jsonserver/branch/merge/concatresource Message-ID: <20070212091922.856AD100A7@code0.codespeak.net> Author: hannosch Date: Mon Feb 12 10:19:19 2007 New Revision: 38535 Modified: z3/jsonserver/branch/merge/concatresource/cachingadapter.py z3/jsonserver/branch/merge/concatresource/concatfileresource.py z3/jsonserver/branch/merge/concatresource/directives.py z3/jsonserver/branch/merge/concatresource/fileresource.py z3/jsonserver/branch/merge/concatresource/meta.py z3/jsonserver/branch/merge/concatresource/resource.py Log: Added some conditional imports in order to surpress deprecation warnings in Zope 2.10 Modified: z3/jsonserver/branch/merge/concatresource/cachingadapter.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/cachingadapter.py (original) +++ z3/jsonserver/branch/merge/concatresource/cachingadapter.py Mon Feb 12 10:19:19 2007 @@ -1,8 +1,13 @@ - from time import time from interfaces import ICachedResource from zope.interface import implements -from zope.app.datetimeutils import rfc1123_date + +try: + from zope.datetime import rfc1123_date +except ImportError: + # Zope < 2.10 + from zope.app.datetimeutils import rfc1123_date + class CachedResource(object): 'Adapts a ContextFile to a cached resource' Modified: z3/jsonserver/branch/merge/concatresource/concatfileresource.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/concatfileresource.py (original) +++ z3/jsonserver/branch/merge/concatresource/concatfileresource.py Mon Feb 12 10:19:19 2007 @@ -14,7 +14,11 @@ from compression import compress import time import zope.component -from zope.component.exceptions import ComponentLookupError +try: + from zope.component.interfaces import ComponentLookupError +except ImportError: + # Zope < 2.10 + from zope.component.exceptions import ComponentLookupError class ConcatFiles(object): '''A resource that concatenates files and compresses the result Modified: z3/jsonserver/branch/merge/concatresource/directives.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/directives.py (original) +++ z3/jsonserver/branch/merge/concatresource/directives.py Mon Feb 12 10:19:19 2007 @@ -2,11 +2,17 @@ from zope.configuration.fields import GlobalObject, Tokens, Path, \ PythonIdentifier, MessageID from zope.schema import TextLine, Text, Id, Choice, Float -from zope.app.security.fields import Permission from fields import PathList from zope.app.component.metadirectives import IBasicViewInformation from zope.app.publisher.browser.metadirectives import IBasicResourceInformation +try: + from zope.security.zcml import Permission +except ImportError: + # Zope < 2.10 + from zope.app.security.fields import Permission + + class IConcatResourceDirective(IBasicResourceInformation): """ Defines a concatenated browser resource Modified: z3/jsonserver/branch/merge/concatresource/fileresource.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/fileresource.py (original) +++ z3/jsonserver/branch/merge/concatresource/fileresource.py Mon Feb 12 10:19:19 2007 @@ -7,10 +7,14 @@ from zope.interface import implements try: - # XXX ??? What zope version needs this? - from zope.app.contenttypes import guess_content_type -except ImportError: - from zope.app.content_types import guess_content_type + from zope.contenttype import guess_content_type +except ImportError: # BBB: Zope < 2.10 + try: + # XXX ??? What zope version needs this? + from zope.app.contenttypes import guess_content_type + except ImportError: + from zope.app.content_types import guess_content_type + import os from interfaces import IContextFile Modified: z3/jsonserver/branch/merge/concatresource/meta.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/meta.py (original) +++ z3/jsonserver/branch/merge/concatresource/meta.py Mon Feb 12 10:19:19 2007 @@ -18,13 +18,13 @@ initializeClass try: - from zope.app.servicenames import Presentation - _layer = 'default' - __pre_3_2__ = True -except: from zope.publisher.interfaces.browser import IDefaultBrowserLayer _layer = IDefaultBrowserLayer __pre_3_2__ = False +except ImportError: + from zope.app.servicenames import Presentation + _layer = 'default' + __pre_3_2__ = True # z3 only allowed_names = ('GET', 'HEAD', 'publishTraverse', 'browserDefault', Modified: z3/jsonserver/branch/merge/concatresource/resource.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/resource.py (original) +++ z3/jsonserver/branch/merge/concatresource/resource.py Mon Feb 12 10:19:19 2007 @@ -1,12 +1,18 @@ -from zope.app.publisher.browser import BrowserView from zope.publisher.interfaces.browser import IBrowserPublisher -from zope.app.datetimeutils import time as timeFromDateTimeString from zope.interface import implements from concatfileresource import ConcatFiles from interfaces import ICachedResource import cachingadapter # force adapter registration try: + from zope.publisher.browser import BrowserView + from zope.datetime import time as timeFromDateTimeString +except ImportError: + # Zope < 2.10 + from zope.app.publisher.browser import BrowserView + from zope.app.datetimeutils import time as timeFromDateTimeString + +try: import Products.Five except ImportError: __five__ = False From ltucker at codespeak.net Mon Feb 12 17:33:55 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Mon, 12 Feb 2007 17:33:55 +0100 (CET) Subject: [z3-checkins] r38590 - z3/deliverance/branches/cache_aware/deliverance Message-ID: <20070212163355.9E7F9100A9@code0.codespeak.net> Author: ltucker Date: Mon Feb 12 17:33:53 2007 New Revision: 38590 Modified: z3/deliverance/branches/cache_aware/deliverance/htmlserialize.py z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Log: support doctype in serialization, use transitional html 4 for middleware output Modified: z3/deliverance/branches/cache_aware/deliverance/htmlserialize.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/htmlserialize.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/htmlserialize.py Mon Feb 12 17:33:53 2007 @@ -3,29 +3,22 @@ html_xsl = """ - + """ -# TODO: this should do real formatting -pretty_html_xsl = """ - - - - - - -""" +# TODO: this should be xsl for real formatting +pretty_html_xsl = html_xsl html_transform = etree.XSLT(etree.XML(html_xsl)) pretty_html_transform = etree.XSLT(etree.XML(pretty_html_xsl)) -def tostring(doc,pretty = False): +def tostring(doc, pretty = False, doctype_pair=None): """ return HTML string representation of the document given @@ -34,9 +27,15 @@ """ if pretty: - return str(pretty_html_transform(doc)) + doc = str(pretty_html_transform(doc)) else: - return str(html_transform(doc)) + doc = str(html_transform(doc)) + + if doctype_pair: + doc = """\n%s""" % (doctype_pair[0], doctype_pair[1], doc) + + return doc + Modified: z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/cache_aware/deliverance/wsgimiddleware.py Mon Feb 12 17:33:53 2007 @@ -213,7 +213,9 @@ in the context of environ. The result is a string containing HTML. """ content = self.get_renderer(environ).render(parseHTML(body)) - return tostring(content) + + return tostring(content, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN", + "http://www.w3.org/TR/html4/loose.dtd")) def rebuild_check(self, environ, start_response): From ltucker at codespeak.net Mon Feb 12 22:51:46 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Mon, 12 Feb 2007 22:51:46 +0100 (CET) Subject: [z3-checkins] r38645 - z3/deliverance/trunk/deliverance Message-ID: <20070212215146.C705710098@code0.codespeak.net> Author: ltucker Date: Mon Feb 12 22:51:41 2007 New Revision: 38645 Added: z3/deliverance/trunk/deliverance/cache_fixture.py - copied unchanged from r38596, z3/deliverance/branches/cache_aware/deliverance/cache_fixture.py z3/deliverance/trunk/deliverance/cache_utils.py - copied unchanged from r38596, z3/deliverance/branches/cache_aware/deliverance/cache_utils.py z3/deliverance/trunk/deliverance/resource_fetcher.py - copied unchanged from r38596, z3/deliverance/branches/cache_aware/deliverance/resource_fetcher.py Modified: z3/deliverance/trunk/deliverance/htmlserialize.py z3/deliverance/trunk/deliverance/test_wsgi.py z3/deliverance/trunk/deliverance/wsgimiddleware.py Log: merging cache_aware branch to trunk 37694:38228 Modified: z3/deliverance/trunk/deliverance/htmlserialize.py ============================================================================== --- z3/deliverance/trunk/deliverance/htmlserialize.py (original) +++ z3/deliverance/trunk/deliverance/htmlserialize.py Mon Feb 12 22:51:41 2007 @@ -3,29 +3,22 @@ html_xsl = """ - + """ -# TODO: this should do real formatting -pretty_html_xsl = """ - - - - - - -""" +# TODO: this should be xsl for real formatting +pretty_html_xsl = html_xsl html_transform = etree.XSLT(etree.XML(html_xsl)) pretty_html_transform = etree.XSLT(etree.XML(pretty_html_xsl)) -def tostring(doc,pretty = False): +def tostring(doc, pretty = False, doctype_pair=None): """ return HTML string representation of the document given @@ -34,9 +27,15 @@ """ if pretty: - return str(pretty_html_transform(doc)) + doc = str(pretty_html_transform(doc)) else: - return str(html_transform(doc)) + doc = str(html_transform(doc)) + + if doctype_pair: + doc = """\n%s""" % (doctype_pair[0], doctype_pair[1], doc) + + return doc + Modified: z3/deliverance/trunk/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/trunk/deliverance/test_wsgi.py (original) +++ z3/deliverance/trunk/deliverance/test_wsgi.py Mon Feb 12 22:51:41 2007 @@ -3,9 +3,14 @@ from lxml import etree from paste.fixture import TestApp from paste.urlparser import StaticURLParser +from paste.response import header_value from deliverance.wsgimiddleware import DeliveranceMiddleware from formencode.doctest_xml_compare import xml_compare from deliverance.htmlserialize import tostring +from deliverance.cache_fixture import CacheFixtureResponseInfo, CacheFixtureApp +from deliverance import cache_utils +from time import time as now +from rfc822 import formatdate static_data = os.path.join(os.path.dirname(__file__), 'test-data', 'static') tasktracker_data = os.path.join(os.path.dirname(__file__), 'test-data', 'tasktracker') @@ -25,6 +30,8 @@ url_app = StaticURLParser(url_data) aggregate_app = StaticURLParser(aggregate_data) + + def html_string_compare(astr, bstr): """ compare to strings containing html based on html @@ -148,8 +155,124 @@ res2 = app.get('/expected.html?notheme') html_string_compare(res.body, res2.body) +def do_cache(renderer_type, name): + # XXX this should be busted up into multiple tests I spose + + theme_data = """ + + theme +
+ + """ + rule_data = """ + + + + """ + + content_data = """ +
foo
+ """ + + expected_data = """ + + + + theme +
foo
+ + """ + + theme_info = CacheFixtureResponseInfo(theme_data) + rule_info = CacheFixtureResponseInfo(rule_data) + content_info = CacheFixtureResponseInfo(content_data) + expected_info = CacheFixtureResponseInfo(expected_data) + + capp = CacheFixtureApp() + capp.map_url('/theme.html',theme_info) + capp.map_url('/rules.xml',rule_info) + capp.map_url('/content.html',content_info) + capp.map_url('/expected.html',expected_info) + + wsgi_app = DeliveranceMiddleware(capp, '/theme.html', '/rules.xml', + renderer_type) + + # check that everything works straight up + app = TestApp(wsgi_app) + res = app.get('/content.html') + res2 = app.get('/expected.html?notheme') + html_string_compare(res.body, res2.body) + + # set some etags on the fixture + theme_info.etag = "theme_etag" + rule_info.etag = "rule_etag" + content_info.etag = "content_etag" + + + # grab the page and make sure an etag comes back + res = app.get('/content.html') + composite_etag = header_value(res.headers, 'etag') + assert(composite_etag is not None and len(composite_etag) > 0) + + # check that deliverance gives 304 when the composite etag is given + res = app.get('/content.html', headers={'If-None-Match': composite_etag}) + status = res.status + assert(status == 304) + + theme_info.etag = 'something_else' + # check that deliverance rebuilds when one of the etags changes + res = app.get('/content.html', headers={'If-None-Match': composite_etag}) + status = res.status + # make sure the response etag changed + assert(header_value(res.headers, 'etag') != composite_etag) + assert(status == 200) + + # clear etags + theme_info.etag = None + rule_info.etag = None + content_info.etag = None + + # make sure there is no more etag + res = app.get('/content.html') + composite_etag = header_value(res.headers, 'etag') + assert(composite_etag is None or len(composite_etag) == 0) + + # test modification dates + then = now() + theme_info.mod_time = then - 10 + rule_info.mod_time = then - 20 + content_info.mod_time = then - 30 + + res = app.get('/content.html') + status = res.status + assert(status == 200) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then)}) + status = res.status + assert(status == 304) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-60)}) + status = res.status + assert(status == 200) + + res = app.get('/content.html', + headers={'If-Modified-Since': formatdate(then-15)}) + status = res.status + assert(status == 200) + + + + + + + + + + RENDERER_TYPES = ['py', 'xslt'] -TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate ] +TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_cache ] def test_all(): for renderer_type in RENDERER_TYPES: for test_func in TEST_FUNCS: Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Mon Feb 12 22:51:41 2007 @@ -1,3 +1,5 @@ + + """ Deliverance theming as WSGI middleware """ @@ -13,14 +15,23 @@ from htmlserialize import tostring from deliverance.utils import DeliveranceError from deliverance.utils import DELIVERANCE_ERROR_PAGE +from deliverance.resource_fetcher import InternalResourceFetcher, ExternalResourceFetcher +from deliverance import cache_utils import sys import datetime import threading import traceback from StringIO import StringIO +from sets import Set DELIVERANCE_BASE_URL = 'deliverance.base-url' +DELIVERANCE_CACHE = 'deliverance.cache' + +IGNORE_EXTENSIONS = ['js','css','gif','jpg','jpeg','pdf','ps','doc','png','ico','mov','mpg','mpeg', 'mp3','m4a', + 'txt','rtf', 'swf', 'wav', 'zip', 'wmv', 'ppt', 'gz', 'tgz', 'jar', 'xls', 'bmp', 'tif', 'tga', + 'hqx', 'avi'] +IGNORE_URL_PATTERN = re.compile("^.*\.(%s)$" % '|'.join(IGNORE_EXTENSIONS)) class DeliveranceMiddleware(object): """ @@ -28,7 +39,7 @@ tranformation as a WSGI middleware component. """ - def __init__(self, app, theme_uri, rule_uri, renderer='py'): + def __init__(self, app, theme_uri, rule_uri, renderer='py', merge_cache_control=False): """ initializer @@ -38,14 +49,15 @@ renderer: selects deliverance render class to utilize when performing transformations, may be 'py' or 'xslt' or a Renderer class + merge_cache_control: if set to True, the cache-control header will + be calculated from the cache-control headers of all component pages + during rendering. If set to False, the requested content's + cache-control headers will be used. (does not affect etag merging) """ self.app = app self.theme_uri = theme_uri self.rule_uri = rule_uri - self._renderer = None - self._cache_time = datetime.datetime.now() - self._timeout = datetime.timedelta(0,10) - self._lock = threading.Lock() + self.merge_cache_control = merge_cache_control if renderer == 'py': import interpreter @@ -58,21 +70,10 @@ else: self._rendererType = renderer - def get_renderer(self,environ): - """ - retrieve the deliverance Renderer representing the transformation this - middlware represents. Renderer may change according to caching rules. - """ - try: - self._lock.acquire() - if not self._renderer or self.cache_expired(): - self._renderer = self.create_renderer(environ) - self._cache_time = datetime.datetime.now() - return self._renderer - finally: - self._lock.release() + def get_renderer(self, environ): + return self.create_renderer(environ) - def create_renderer(self,environ): + def create_renderer(self, environ): """ construct a new deliverance Renderer from the information passed to the initializer. A new copy @@ -85,7 +86,7 @@ self.theme_uri) def reference_resolver(href, parse, encoding=None): - text = self.get_resource(environ,href) + text = self.get_resource(environ, href) if parse == "xml": return etree.XML(text) if parse == "html": @@ -117,13 +118,6 @@ rule_uri=self.rule_uri, reference_resolver=reference_resolver) - - def cache_expired(self): - """ - returns true if the stored Renderer should be refreshed - """ - return self._cache_time + self._timeout < datetime.datetime.now() - def rule(self, environ): """ retrieves the data referred to by the rule_uri passed to the @@ -144,7 +138,7 @@ initializer. """ try: - return self.get_resource(environ,self.theme_uri) + return self.get_resource(environ, self.theme_uri) except Exception, message: message.public_html = 'Unable to retrieve theme page from %s: %s' % ( self.theme_uri, message) @@ -158,45 +152,46 @@ using the transformation specified in the initializer. """ - try: - qs = environ.get('QUERY_STRING', '') - environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) - notheme = 'notheme' in qs - if notheme: - return self.app(environ, start_response) - if 'HTTP_ACCEPT_ENCODING' in environ: - del environ['HTTP_ACCEPT_ENCODING'] - - status, headers, body = intercept_output( - environ, self.app, - self.should_intercept, - start_response) - - # ignore non-html responses - if status is None: - return body - - # don't theme html snippets - if self.hasHTMLTag(body): - body = self.filter_body(environ, body) - - replace_header(headers, 'content-length', str(len(body))) - replace_header(headers, 'content-type', 'text/html; charset=utf-8') - start_response(status, headers) - return [body] + qs = environ.get('QUERY_STRING', '') + environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) + environ[DELIVERANCE_CACHE] = {} + notheme = 'notheme' in qs + if notheme: + # eliminate the deliverance notheme query argument for the subrequest + if qs == 'notheme': + environ['QUERY_STRING'] = '' + elif qs.endswith('¬heme'): + environ['QUERY_STRING'] = qs[:-len('¬heme')] + return self.app(environ, start_response) - except DeliveranceError, message: - stack = StringIO() - traceback.print_exception(sys.exc_info()[0], - sys.exc_info()[1], - sys.exc_info()[2], - file=stack) - status = "500 Internal Server Error" - headers = [('Content-type','text/html')] - start_response(status,headers) - errpage = DELIVERANCE_ERROR_PAGE % (message,stack.getvalue()) - return [ errpage ] + # unsupported + if 'HTTP_ACCEPT_ENCODING' in environ: + environ['HTTP_ACCEPT_ENCODING'] = '' + if 'HTTP_IF_MATCH' in environ: + del environ['HTTP_IF_MATCH'] + if 'HTTP_IF_UNMODIFIED_SINCE' in environ: + del environ['HTTP_IF_UNMODIFIED_SINCE'] + + status, headers, body = self.rebuild_check(environ, start_response) + + # non-html responses, or rebuild is not necessary: bail out + if status is None: + return body + + # perform actual themeing + body = self.filter_body(environ, body) + replace_header(headers, 'content-length', str(len(body))) + replace_header(headers, 'content-type', 'text/html; charset=utf-8') + + cache_utils.merge_cache_headers(environ, + environ[DELIVERANCE_CACHE], + headers, + self.merge_cache_control) + + start_response(status, headers) + return [body] + def should_intercept(self, status, headers): """ returns true if the status and headers given @@ -205,7 +200,7 @@ """ type = header_value(headers, 'content-type') if type is None: - return False + return True # yerg, 304s can have no content-type return type.startswith('text/html') or type.startswith('application/xhtml+xml') def filter_body(self, environ, body): @@ -214,79 +209,133 @@ in the context of environ. The result is a string containing HTML. """ content = self.get_renderer(environ).render(parseHTML(body)) - return tostring(content) - def get_resource(self, environ, uri): - """ - retrieve the data referred to by the uri given. - """ - internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) - uri = urlparse.urljoin(internalBaseURL, uri) + return tostring(content, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN", + "http://www.w3.org/TR/html4/loose.dtd")) - if internalBaseURL and uri.startswith(internalBaseURL): - return self.get_internal_resource(environ, uri[len(internalBaseURL):]) - else: - return self.get_external_resource(uri) - def relative_uri(self, uri): - """ - returns true if uri is relative, false if - the uri is absolute. - """ - if re.search(r'^[a-zA-Z]+:', uri): - return False - else: - return True + def rebuild_check(self, environ, start_response): + # perform the request for content - def get_external_resource(self, uri): - """ - get the data referred to by the uri given - using urllib (not through the wrapped app) - """ - f = urllib.urlopen(uri) - content = f.read() - f.close() - return content + content_url = construct_url(environ) + + etag_map = {} + if 'HTTP_IF_NONE_MATCH' in environ: + etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + tag = etag_map.get(content_url, None) + environ['HTTP_IF_NONE_MATCH'] = tag + if tag: + environ['HTTP_IF_NONE_MATCH'] = tag + else: + if 'HTTP_IF_NONE_MATCH' in environ: + del environ['HTTP_IF_NONE_MATCH'] - def get_internal_resource(self, in_environ, uri): + + status, headers, body = intercept_output(environ, self.app, + self.should_intercept, + start_response) + + + if status is None: + # should_intercept says this isn't HTML, we're done + return (None, None, body) + + if self.should_ignore_url(content_url): + start_response(status, headers) + return (None, None, [body]) + + # cache the response so we can look at its headers later + environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) + + # it was modified or an error, give it back for themeing + if not status.startswith('304'): + # if it's not a full HTML page, skip it + if not self.hasHTMLTag(body): + start_response(status, headers) + return (None, None, [body]) + + # send it back for rebuild + return (status, headers, body) + + # got 304 Not Modified for content, check other resources + rules = etree.XML(self.rule(environ)) + resources = self.get_resource_uris(rules) + if self.any_modified(environ, resources, etag_map): + # something changed, + # get the content explicitly and give it back + if 'HTTP_IF_MODIFIED_SINCE' in environ: + del environ['HTTP_IF_MODIFIED_SINCE'] + if 'HTTP_IF_NONE_MATCH' in environ: + del environ['HTTP_IF_NONE_MATCH'] + environ['CACHE-CONTROL'] = 'no-cache' + + status, headers, body = intercept_output(environ, self.app) + + if not self.hasHTMLTag(body): + # XXX yarg, we didn't care about it! + start_response(status, headers) + return (None, None, [body]) + + environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) + return (status, headers, body) + + # nothing was modified, give back a 304 + cache_utils.merge_cache_headers(environ, + environ[DELIVERANCE_CACHE], + headers, + self.merge_cache_control) + start_response('304 Not Modified', headers) + + return (None,None,[]) + + def any_modified(self, environ, resources, etag_map): """ - get the data referred to by the uri given - by using the wrapped WSGI application + returns a boolean indicating whether any of the uris in the resources + list have been modified. if an entry for the uri exists in the map + etag_map, the value will be used to check the resource using an + if-none-match http header. if an if-not-modified check is desired, + it should be present in environ. """ - - if 'paste.recursive.include' in in_environ: - environ = in_environ['paste.recursive.include'].original_environ.copy() - else: - environ = in_environ.copy() + moddate = None + + if 'HTTP_IF_MODIFIED_SINCE' in environ: + moddate = environ['HTTP_IF_MODIFIED_SINCE'] - if not uri.startswith('/'): - uri = '/' + uri - environ['PATH_INFO'] = uri - base = in_environ[DELIVERANCE_BASE_URL] - scheme, netloc, path, qs, fragment = urlparse.urlsplit(base) - environ['SCRIPT_NAME'] = path - environ['REQUEST_METHOD'] = 'GET' - environ['CONTENT_LENGTH'] = '0' - environ['wsgi.input'] = StringIO('') - environ['CONTENT_TYPE'] = '' - if environ['QUERY_STRING']: - environ['QUERY_STRING'] += '¬heme' - else: - environ['QUERY_STRING'] = 'notheme' + for uri in resources: + if (self.check_modification(environ, uri, + moddate, + etag_map.get(uri,None))): + return True - if 'HTTP_ACCEPT_ENCODING' in environ: - environ['HTTP_ACCEPT_ENCODING'] = '' + return False - if 'paste.recursive.include' in in_environ: - # Try to do the redirect this way... - includer = in_environ['paste.recursive.include'] - res = includer(uri, environ) - return res.body + def get_resource(self, environ, uri): + """ + retrieve the content from the uri given, + uses cache if possible. throws exception if + response is not 200 + """ + if uri in environ[DELIVERANCE_CACHE]: + response = environ[DELIVERANCE_CACHE][uri] + if response[0].startswith('200'): + return response[2] + + fetcher = self.get_fetcher(environ, uri) + + + # eliminate validation headers, we want the content + if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: + del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] + if 'HTTP_IF_NONE_MATCH' in fetcher.environ: + del fetcher.environ['HTTP_IF_NONE_MATCH'] + fetcher.environ['CACHE-CONTROL'] = 'no-cache' + - path_info = environ['PATH_INFO'] - status, headers, body = intercept_output(environ, self.app) - if not status.startswith('200'): + status, headers, body = fetcher.wsgi_get() + + if not status.startswith('200'): + path_info = uri loc = header_value(headers, 'location') if loc: loc = ' location=%r' % loc @@ -296,7 +345,82 @@ "Request for internal resource at %s (%r) failed with status code %r%s" % (construct_url(environ), path_info, status, loc)) + + environ[DELIVERANCE_CACHE][uri] = (status, headers, body) + return body + + + def get_fetcher(self, environ, uri): + """ + retrieve an object which is appropriate for fetching the + uri specified. + """ + internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) + uri = urlparse.urljoin(internalBaseURL, uri) + + if internalBaseURL and uri.startswith(internalBaseURL): + return InternalResourceFetcher(environ, uri[len(internalBaseURL):], + self.app) + else: + return ExternalResourceFetcher(uri) + + + def get_resource_uris(self, rules): + """ + retrieves a list of uris pointing to the resources that + are components of rendering (excluding content) + """ + resources = Set() + resources.add(self.rule_uri) + resources.add(self.theme_uri) + + for rule in rules: + href = rule.get("href",None) + if href is not None: + resources.add(href) + + return list(resources) + + + def check_modification(self, environ, uri, httpdate_since=None, etag=None): + """ + if httpdate_since is set to an httpdate the If-Modified-Since HTTP header + is used to check for modification + + if etag is set to an etag for the resource, the If-None-Match HTTP header + is used to check for modification + + the resulting (status, headers, body) tuple for the request is stored in + environ[DELIVERANCE_CACHE][uri]. + + """ + + fetcher = self.get_fetcher(environ, uri) + + if httpdate_since: + fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = httpdate_since + else: + if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: + del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] + + + if etag: + fetcher.environ['HTTP_IF_NONE_MATCH'] = etag + else: + if 'HTTP_IF_NONE_MATCH' in fetcher.environ: + del fetcher.environ['HTTP_IF_NONE_MATCH'] + + + status, headers, body = fetcher.wsgi_get() + environ[DELIVERANCE_CACHE][uri] = (status, headers, body) + + if status.startswith('304'): # Not Modified + return False + + return True + + HTML_DOC_PAT = re.compile(r"^.*<\s*html(\s*|>).*$",re.I|re.M) def hasHTMLTag(self, body): @@ -308,6 +432,11 @@ """ return self.HTML_DOC_PAT.search(body) is not None + + def should_ignore_url(self, url): + # blacklisting can happen here as well + return re.match(IGNORE_URL_PATTERN, url) is not None + def make_filter(app, global_conf, theme_uri=None, rule_uri=None): assert theme_uri is not None, ( From ianb at codespeak.net Thu Feb 15 22:40:11 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 15 Feb 2007 22:40:11 +0100 (CET) Subject: [z3-checkins] r38970 - in z3/deliverance/DeliveranceVHoster/trunk: docs dvhoster tests Message-ID: <20070215214011.C860B1007C@code0.codespeak.net> Author: ianb Date: Thu Feb 15 22:40:09 2007 New Revision: 38970 Added: z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py (contents, props changed) z3/deliverance/DeliveranceVHoster/trunk/dvhoster/util.py (contents, props changed) z3/deliverance/DeliveranceVHoster/trunk/tests/test_init_func.py (contents, props changed) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Log: Added configurable initialization function Added: z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py Thu Feb 15 22:40:09 2007 @@ -0,0 +1,37 @@ +# the init_domain setting points to a file or module (a file like this +# one). It allows you to define a function that will be run on any +# newly created domain. Here you can put in default settings. This +# example is what we use for openplans.org + +import re + +rule_data = """\ + + + + + +""" + + +def init_domain(domain_info, app_conf): + domain = domain_info.domain + match = re.search(r'^(.*)\.openplans\.org$', domain, re.I) + if not match: + # Don't try to set up domains we don't recognize + return + project = match.group(1) + remote_uri = ( + '%s/VirtualHostBase/http/%s:80/openplans/projects/%s/VirtualHostRoot' + % (app_conf['zope_location'], + domain, + project)) + domain_info.remote_uris = [ + {'path': '', + 'remote_uri': remote_uri}] + domain_info.theme_uri = app_conf['default_theme_uri'] + domain_info.set_rule_file( + 'rule.xml', rule_data) + + + Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Thu Feb 15 22:40:09 2007 @@ -181,8 +181,6 @@ remote_uris = descriptors.json_converter( persist.file_property('remote_uris.txt')) theme_uri = persist.file_property('theme_uri.txt') - theme_id = persist.file_property('theme_id.txt') - rule_ids = persist.file_property('rule_ids.txt') redirects = descriptors.json_converter( persist.file_property('redirects.txt', default=())) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Thu Feb 15 22:40:09 2007 @@ -12,6 +12,7 @@ from dvhoster.dataprovider import DataProvider, ProviderApp from dvhoster import current_environ from dvhoster.debuginterp import Renderer +from dvhoster.util import load_func def norm_path(urlpath): if not urlpath: @@ -24,6 +25,11 @@ def __init__(self, app_conf): data_dir = app_conf['data_dir'] + init_domain = app_conf.get('init_domain') + if init_domain: + init_domain = load_func(init_domain, 'init_domain') + self.init_domain = init_domain + self.app_conf = app_conf self.provider = DataProvider(data_dir) self.rewrite_links = asbool(app_conf.get('rewrite_links', True)) @@ -35,6 +41,11 @@ domain = domain.split(':', 1)[0] domain = self.provider.normalize(domain) domain_info = self.provider.domain(domain) + print domain_info, domain_info.initialized + if not domain_info.initialized: + domain_info.initialize() + if self.init_domain: + self.init_domain(domain_info, self.app_conf) environ['dvhoster.domain_info'] = domain_info environ['dvhoster.base_url'] = construct_url( environ, with_query_string=False, Added: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/util.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/util.py Thu Feb 15 22:40:09 2007 @@ -0,0 +1,38 @@ +import os +import types +from paste.util.import_string import eval_import + +def load_func(description, default_name): + """ + Load a function from the description. The description can be a + Python module/identifier, or a filename (which must end in .py). + If a filename, then we look for default_name in the file as the + function to load. The Python module/identifier may be a function + itself, but if it is a module then we will again look for the + function by name. + """ + if description.endswith('.py'): + if not os.path.exists(description): + raise OSError( + "No file exists by the name %r" % description) + f = open(description, 'r') + c = f.read() + f.close() + ns = {'__file__': os.path.abspath(description)} + exec c in ns + if default_name not in ns: + raise NameError( + "The file %r must provide a function by the name %s" + % (description, default_name)) + return ns[default_name] + else: + obj = eval_import(description) + if isinstance(obj, types.ModuleType): + value = getattr(obj, default_name, None) + if not value: + raise NameError( + "The module %s must provide a function by the name %s" + % (description, default_name)) + return value + else: + return obj Added: z3/deliverance/DeliveranceVHoster/trunk/tests/test_init_func.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_init_func.py Thu Feb 15 22:40:09 2007 @@ -0,0 +1,32 @@ +import os +import shutil +import shutil +from paste.fixture import TestApp +from dvhoster.wsgiapp import make_app +import httplib +import simplejson + +example = os.path.join(os.path.dirname(os.path.dirname(__file__)), + 'docs', 'example_init_domain.py') + +data_filename = os.path.join(os.path.dirname(__file__), 'test-data') +wsgi_app = make_app({}, + init_domain=example, + zope_location='http://localhost:8080', + default_theme_uri='http://yahoo.com', + data_dir=data_filename) +app = TestApp(wsgi_app) + +def test_init_domain(): + foo_dir = os.path.join( + data_filename, 'foo.openplans.org') + if os.path.exists(foo_dir): + shutil.rmtree(foo_dir) + res = app.get('/.deliverance/remote_uris', + extra_environ={'HTTP_HOST': 'foo.openplans.org'}) + body = simplejson.loads(res.body) + expected = 'http://localhost:8080/VirtualHostBase/http/foo.openplans.org:80/openplans/projects/foo/VirtualHostRoot' + got = body[0]['remote_uri'] + print got + print expected + assert got == expected From hannosch at codespeak.net Tue Feb 20 23:00:51 2007 From: hannosch at codespeak.net (hannosch at codespeak.net) Date: Tue, 20 Feb 2007 23:00:51 +0100 (CET) Subject: [z3-checkins] r39247 - z3/jsonserver/branch/merge/concatresource Message-ID: <20070220220051.0C71710131@code0.codespeak.net> Author: hannosch Date: Tue Feb 20 23:00:49 2007 New Revision: 39247 Modified: z3/jsonserver/branch/merge/concatresource/meta.py Log: Fixed deprecation warning for the provideAdapter method on Zope >= 3.3. Modified: z3/jsonserver/branch/merge/concatresource/meta.py ============================================================================== --- z3/jsonserver/branch/merge/concatresource/meta.py (original) +++ z3/jsonserver/branch/merge/concatresource/meta.py Tue Feb 20 23:00:49 2007 @@ -26,6 +26,12 @@ _layer = 'default' __pre_3_2__ = True +try: + from zope.component import zcml + __pre_3_3__ = False +except: + __pre_3_3__ = True + # z3 only allowed_names = ('GET', 'HEAD', 'publishTraverse', 'browserDefault', 'request', '__call__') @@ -91,9 +97,17 @@ name, IBrowserRequest, factory, layer), ) else: - _context.action( - discriminator = ('resource', name, IBrowserRequest, layer), - callable = handler, - args = ('provideAdapter', - (layer,), Interface, name, factory, _context.info), - ) + if __pre_3_3__: + _context.action( + discriminator = ('resource', name, IBrowserRequest, layer), + callable = handler, + args = ('provideAdapter', + (layer,), Interface, name, factory, _context.info), + ) + else: + _context.action( + discriminator = ('resource', name, IBrowserRequest, layer), + callable = handler, + args = ('registerAdapter', + factory, (layer,), Interface, name, _context.info), + ) From ltucker at codespeak.net Wed Feb 28 16:41:34 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 28 Feb 2007 16:41:34 +0100 (CET) Subject: [z3-checkins] r39588 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070228154134.88FAD10108@code0.codespeak.net> Author: ltucker Date: Wed Feb 28 16:41:32 2007 New Revision: 39588 Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py Log: vhoster needs ohm dev to pick up small change in put behavior Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.py Wed Feb 28 16:41:32 2007 @@ -12,7 +12,7 @@ 'Deliverance', 'WSGIFilter', 'HTTPEncode', - 'OHM', + 'OHM==dev', 'wsgi_intercept', ], dependency_links=[ From ltucker at codespeak.net Wed Feb 28 18:06:50 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Wed, 28 Feb 2007 18:06:50 +0100 (CET) Subject: [z3-checkins] r39608 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070228170650.2E89E10123@code0.codespeak.net> Author: ltucker Date: Wed Feb 28 18:06:47 2007 New Revision: 39608 Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py Log: adding version requirement to satisfy setuptools Modified: z3/deliverance/DeliveranceVHoster/trunk/setup.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/setup.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/setup.py Wed Feb 28 18:06:47 2007 @@ -12,7 +12,7 @@ 'Deliverance', 'WSGIFilter', 'HTTPEncode', - 'OHM==dev', + 'OHM==dev,>0.1', 'wsgi_intercept', ], dependency_links=[ From jinty at codespeak.net Thu Mar 1 14:44:30 2007 From: jinty at codespeak.net (jinty at codespeak.net) Date: Thu, 1 Mar 2007 14:44:30 +0100 (CET) Subject: [z3-checkins] r39631 - z3/sqlos/trunk/src/sqlos Message-ID: <20070301134430.D7DAD1011A@code0.codespeak.net> Author: jinty Date: Thu Mar 1 14:44:29 2007 New Revision: 39631 Modified: z3/sqlos/trunk/src/sqlos/adapter.py Log: Fix a bug when testing with newer versions of SQLObject. Modified: z3/sqlos/trunk/src/sqlos/adapter.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/adapter.py (original) +++ z3/sqlos/trunk/src/sqlos/adapter.py Thu Mar 1 14:44:29 2007 @@ -181,5 +181,7 @@ except ImportError: import sqlite self.module = sqlite + # Check if we are a memory based database + self._memory = ':memory:' in connection.getTypeInfo().getDSN() super(SQLiteAdapter, self).__init__(connection) self.supportTransactions = True From ltucker at codespeak.net Thu Mar 1 23:05:06 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Thu, 1 Mar 2007 23:05:06 +0100 (CET) Subject: [z3-checkins] r39645 - z3/deliverance/DeliveranceVHoster/trunk/dvhoster Message-ID: <20070301220506.4BB9010131@code0.codespeak.net> Author: ltucker Date: Thu Mar 1 23:05:03 2007 New Revision: 39645 Added: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/api_wrapper.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/cli.py Log: adding a crumby cli interface that may be of some value temporarily Added: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/api_wrapper.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/api_wrapper.py Thu Mar 1 23:05:03 2007 @@ -0,0 +1,159 @@ +# +# exposes the restful deliverance API +# as python calls +# + +from StringIO import StringIO +from paste.wsgilib import intercept_output +from paste.proxy import TransparentProxy +from paste.request import construct_url +from deliverance.utils import DeliveranceError +import simplejson + +class DeliveranceAdmin: + + def __init__(self, domain, force_host=None): + self.domain = domain + self.force_host = force_host + + def set_rules(self, rule_data): + return self.put_data('/.deliverance/rules/rule.xml', rule_data) + + def get_rules(self): + return self.get_data('/.deliverance/rules/rule.xml') + + def set_theme_uri(self, theme_uri): + return self.put_data('/.deliverance/theme_uri', theme_uri) + + def get_theme_uri(self): + return self.get_data('/.deliverance/theme_uri') + + def set_domain(self, new_domain): + rc = self.put_data('/.deliverance/domain', new_domain) + self.domain = new_domain + return rc + + def get_domain(self): + return self.get_data('/.deliverance/domain') + + def set_static_file(self, path, data): + return self.put_data('/.deliverance/static/%s' % path, data) + + def static_mkdir(self, subdir): + return self.mkdir('/.deliverance/static/%s' % subdir) + + #----------------------------- + + def get_redirects(self): + return simplejson.loads(self.get_data('/.deliverance/redirects')) + + def set_redirects(self, python_map): + return self.put_data('/.deliverance/redirectss', simplejson.dumps(python_map)) + + def set_redirects_json(self, json_str): + return self.put_data('/.deliverance/redirects', json_str, content_type="application/json; charset=utf8") + + def append_redirect(self, path, redirect, comment, prefix=False): + cur_redirects = self.get_redirects() + rmap = {} + if prefix: + rmap['prefix'] = path + else: + rmap['path'] = path + rmap['rewrite'] = remote_uri + rmap['comment'] = comment + + new_redirects = [m for m in cur_redirects if 'path' in m and m['path'] != path] + new_redirects.append(rmap) + + return self.set_redirects(new_redirects) + + + #-------------------------------- + + + def get_remote_uris(self): + return simplejson.loads(self.get_data('/.deliverance/remote_uris')) + + def set_remote_uris(self, python_map): + return self.put_data('/.deliverance/remote_uris', simplejson.dumps(python_map)) + + def set_remote_uris_json(self, json_str): + return self.put_data('/.deliverance/remote_uris', json_str, content_type="application/json; charset=utf8") + + def append_remote_uri(self, path, remote_uri, comment): + cur_uris = self.get_remote_uris() + rmap = {} + rmap['path'] = path + rmap['remote_uri'] = remote_uri + rmap['comment'] = comment + + new_remote_uris = [m for m in cur_uris if 'path' in m and m['path'] != path] + new_remote_uris.append(rmap) + + return self.set_remote_uris(new_remote_uris) + + #------------------------------------- + + def mkdir(self, subdir): + env = {} + + env['wsgi.input'] = StringIO('') + env['wsgi.url_scheme'] = 'http' + env['REQUEST_METHOD'] = 'MKCOL' + env['HTTP_HOST'] = self.domain + env['SCRIPT_INFO'] = '' + env['PATH_INFO'] = subdir + env['CONTENT_LENGTH'] = '0' + + app = TransparentProxy(force_host=self.force_host) + status, headers, body = intercept_output(env, app) + + if not status.startswith('201'): + raise DeliveranceError('Error creating directory %s got %s [%s]' % (subdir, status, body)) + + return True + + def put_data(self, put_path, data, content_type=None): + env = {} + env['wsgi.version'] = (1,0) + env['wsgi.input'] = StringIO(data) + env['wsgi.url_scheme'] = 'http' + env['HTTP_HOST'] = self.domain + env['SCRIPT_INFO'] = '' + env['REQUEST_METHOD'] = 'PUT' + env['PATH_INFO'] = put_path + if content_type: + env['CONTENT_TYPE'] = content_type + + app = TransparentProxy(force_host=self.force_host) + status, headers, body = intercept_output(env, app) + + if not (status.startswith('200') or status.startswith('204')): + raise DeliveranceError('Error setting %s got %s [%s]' % (put_path, status, body)) + + return True + + + def get_data(self, get_path): + env = {} + + env['wsgi.version'] = (1,0) + env['wsgi.input'] = StringIO('') + env['wsgi.url_scheme'] = 'http' + env['REQUEST_METHOD'] = 'GET' + env['HTTP_HOST'] = self.domain + env['SCRIPT_INFO'] = '' + env['PATH_INFO'] = get_path + env['CONTENT_LENGTH'] = '0' + + + app = TransparentProxy(force_host=self.force_host) + status, headers, body = intercept_output(env, app) + + if not status.startswith('200'): + raise DeliveranceError('Error getting %s got %s [%s]' % (get_path, status, body)) + + return body + + Added: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/cli.py ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/cli.py Thu Mar 1 23:05:03 2007 @@ -0,0 +1,157 @@ +# +# command line interface to deliverance vhoster +# + +import sys +import optparse +import traceback +from api_wrapper import DeliveranceAdmin + + +def do_get_domain(adm, *args): + print adm.get_domain() + +def do_set_domain(adm, name): + if adm.set_domain(name): + print "[-] OK" + +def do_get_rules(adm, *args): + print adm.get_rules() + +def do_set_rules(adm, rules): + if rules.startswith("\'") or rules.startswith('\"'): + if not rules.endswith(rules[0]): + print "[X] mismatched quotes" + return + data = rules[1:-1] + else: + data = open(rules).read() + + if adm.set_rules(data): + print "[-] OK" + + +def do_get_theme_uri(adm, *args): + print adm.get_theme_uri() + +def do_set_theme_uri(adm, uri): + if adm.set_theme_uri(uri): + print "[-] OK" + +def do_get_redirects(adm, *args): + print adm.get_redirects() + +def do_set_redirects(adm, json): + if adm.set_redirects_json(json): + print "[-] OK" + +def do_append_redirect(adm, path, redirect): + if adm.append_redirect(path, redirect, '?'): + print "[-] OK" + +def do_append_redirect_prefix(adm, path, redirect): + if adm.append_redirect(path, redirect, '?', prefix=True): + print "[-] OK" + +def do_get_remote_uris(adm, *args): + print adm.get_remote_uris() + +def do_set_remote_uris(adm, json): + if adm.set_remote_uris_json(json): + print "[-] OK" + +def do_append_remote_uri(adm, path, remote_uri): + if adm.append_remote_uri(path, remote_uri, '?'): + print "[-] OK" + + +def do_help(adm, command=None): + if command is None: + print "available commands:" + print "-------------------" + for cmd in COMMANDS: + print cmd + return + if command in COMMANDS_HELP: + print COMMANDS_HELP[command] + return + else: + print "sorry, no help for ", command + +COMMANDS = { + 'get_domain': do_get_domain, + 'set_domain': do_set_domain, + 'get_rules': do_get_rules, + 'set_rules': do_set_rules, + 'get_theme_uri': do_get_theme_uri, + 'set_theme_uri': do_set_theme_uri, + 'get_redirects': do_get_redirects, + 'set_redirects': do_set_redirects, + 'append_redirect': do_append_redirect, + 'append_redirect_prefix': do_append_redirect_prefix, + 'get_remote_uris': do_get_remote_uris, + 'set_remote_uris': do_set_remote_uris, + 'append_remote_uri': do_append_remote_uri, + 'help': do_help +} + +COMMANDS_HELP = { + 'get_domain': "retrieves domain name", + 'set_domain': "eg: set_domain foo.wooley.bar", + 'get_rules': "retrieves rules", + 'set_rules': "eg: set_rules '' or set_rules some_file.xml", + 'get_theme_uri': "retrieve theme uri", + 'set_theme_uri': "eg: set_theme_uri http://foo.org/theme.html", + 'get_redirects': "retrieve current list of redirects", + 'set_redirects': "eg set_redirects [{'path': '/', 'rewrite': 'http://www.quux.org/blah', 'comment': 'go to quux'}, {...}]", + 'append_redirect': "eg append_redirect / http://www.quux.org/blah" , + 'append_redirect_prefix': "eg append_redirect / http://www.quux.org/blah" , + 'get_remote_uris': "retrieve current list of remote_uris", + 'set_remote_uris': "eg set_remote_uris [{'path': '/', 'remote_uri': 'http://www.quux.org/blah', 'comment': 'go to quux'}, {...}]", + 'append_remote_uri': "eg append_remote_uri / http://www.quux.org/blah" , +} + +def repl(adm): + done = False + + while not done: + sys.stdout.write("> ") + cmd_buf = raw_input().split(' ') + if cmd_buf[0] in COMMANDS: + try: + COMMANDS[cmd_buf[0]](adm, *cmd_buf[1:]) + except: + traceback.print_exc() + else: + print "[X] unknown command" + +def main(argv=None): + if argv is None: + argv = sys.argv + + usage = "usage: %prog domain [options]" + parser = optparse.OptionParser(usage=usage) + parser.add_option('-F','--force-host', + help="specify the administration server to contact", + dest='force_host', + default=None) + options, args = parser.parse_args(argv) + + if len(args) < 2: + parser.print_usage() + sys.exit(0) + + domain = args[1] + if options.force_host: + print "configuring domain [%s] at %s" % (domain, options.force_host) + else: + print "configuring domain [%s]" % domain + + + adm = DeliveranceAdmin(domain, force_host=options.force_host) + + + repl(adm) + +if __name__ == '__main__': + main(sys.argv) From ltucker at codespeak.net Mon Mar 5 06:25:39 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Mon, 5 Mar 2007 06:25:39 +0100 (CET) Subject: [z3-checkins] r39930 - in z3/deliverance/trunk: . deliverance Message-ID: <20070305052539.8ABD310063@code0.codespeak.net> Author: ltucker Date: Mon Mar 5 06:25:37 2007 New Revision: 39930 Modified: z3/deliverance/trunk/deliverance/resource_fetcher.py z3/deliverance/trunk/deliverance/wsgimiddleware.py z3/deliverance/trunk/setup.py Log: handle file urls again Modified: z3/deliverance/trunk/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/trunk/deliverance/resource_fetcher.py (original) +++ z3/deliverance/trunk/deliverance/resource_fetcher.py Mon Mar 5 06:25:37 2007 @@ -4,6 +4,7 @@ from paste.proxy import TransparentProxy from paste.request import construct_url from paste.response import header_value +from paste.fileapp import FileApp import urlparse from deliverance.utils import DeliveranceError @@ -75,6 +76,54 @@ loc)) return body +class FileResourceFetcher(object): + def __init__(self, environ, uri, headers_only=False): + self.environ = environ.copy() + self.uri = uri + + uri_parts = urlparse.urlparse(self.uri) + self.environ['PATH_INFO'] = uri_parts[2] + self.environ['SCRIPT_INFO'] = '' + self.environ['wsgi.scheme'] = 'file' + if len(uri_parts[4]) > 0: + self.environ['QUERY_STRING'] = uri_parts[4] + '¬heme' + else: + self.environ['QUERY_STRING'] = 'notheme' + + if headers_only: + self.environ['REQUEST_METHOD'] = 'HEAD' + else: + self.environ['REQUEST_METHOD'] = 'GET' + + self.environ['CONTENT_LENGTH'] = '0' + self.environ['wsgi.input'] = StringIO('') + self.environ['CONTENT_TYPE'] = '' + + if 'HTTP_ACCEPT_ENCODING' in self.environ: + del self.environ['HTTP_ACCEPT_ENCODING'] + + def wsgi_get(self): + path = urlparse.urlparse(self.uri)[2] + file_app = FileApp(path) + + return intercept_output(self.environ, file_app) + + + def get(self): + path_info = self.environ['PATH_INFO'] + status, headers, body = self.wsgi_get() + + if not status.startswith('200'): + loc = header_value(headers, 'location') + if loc: + loc = ' location=%r' % loc + else: + loc = '' + raise DeliveranceError( + "Request for file at %s (%r) failed with status code %r%s" + % (construct_url(self.environ), path_info, status, + loc)) + return body class ExternalResourceFetcher(object): def __init__(self, uri, headers_only=False): Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Mon Mar 5 06:25:37 2007 @@ -15,7 +15,7 @@ from htmlserialize import tostring from deliverance.utils import DeliveranceError from deliverance.utils import DELIVERANCE_ERROR_PAGE -from deliverance.resource_fetcher import InternalResourceFetcher, ExternalResourceFetcher +from deliverance.resource_fetcher import InternalResourceFetcher, FileResourceFetcher, ExternalResourceFetcher from deliverance import cache_utils import sys import datetime @@ -359,7 +359,10 @@ internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) uri = urlparse.urljoin(internalBaseURL, uri) - if internalBaseURL and uri.startswith(internalBaseURL): + if urlparse.urlparse(uri)[0] == 'file': + return FileResourceFetcher(environ, uri) + + elif internalBaseURL and uri.startswith(internalBaseURL): return InternalResourceFetcher(environ, uri[len(internalBaseURL):], self.app) else: Modified: z3/deliverance/trunk/setup.py ============================================================================== --- z3/deliverance/trunk/setup.py (original) +++ z3/deliverance/trunk/setup.py Mon Mar 5 06:25:37 2007 @@ -18,7 +18,7 @@ packages=find_packages(exclude=[]), zip_safe=False, install_requires=[ - 'lxml', + 'lxml==1.2', 'Paste', 'FormEncode', 'elementtree', From ltucker at codespeak.net Tue Mar 6 01:23:32 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Tue, 6 Mar 2007 01:23:32 +0100 (CET) Subject: [z3-checkins] r39972 - z3/deliverance/DeliveranceVHoster/trunk/dvhoster Message-ID: <20070306002332.143AD10069@code0.codespeak.net> Author: ltucker Date: Tue Mar 6 01:23:29 2007 New Revision: 39972 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py Log: Don't report errors with onerror=ignore in VHoster renderer subclass. None from format_error means ignore the error, though this could be changed in Deliverance... Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/debuginterp.py Tue Mar 6 01:23:29 2007 @@ -10,8 +10,8 @@ def format_error(self, message, rule, elts=None): error = super(PyRenderer, self).format_error(message, rule, elts) if error is None: - error = etree.Element('div') - error.text = 'Error with no message (%s)' % message + return None + error_container = etree.Element('div') error_container.attrib['style'] = self.error_style if not current_environ.get('dvhoster.has_errors'): From ianb at codespeak.net Sat Mar 10 00:27:03 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Sat, 10 Mar 2007 00:27:03 +0100 (CET) Subject: [z3-checkins] r40151 - in z3/deliverance/DeliveranceVHoster/trunk: . docs dvhoster Message-ID: <20070309232703.1831A1006F@code0.codespeak.net> Author: ianb Date: Sat Mar 10 00:26:59 2007 New Revision: 40151 Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Log: Added a request headers override, with example for openplans for setting the header X-Openplans-Project to the project name. Also added a request header scrubber, so we can scrub all X-Openplans headers on the way in. Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/development.ini (original) +++ z3/deliverance/DeliveranceVHoster/trunk/development.ini Sat Mar 10 00:26:59 2007 @@ -22,6 +22,9 @@ #debug_bodies = true # To disable the rewriting of links: #rewrite_links = false +# Use this to remove certain headers from any request: +# (note: this matches the *environmental key*, not the original header) +#clean_environ_headers_regex = ^HTTP_X_OPENPLANS # WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* # Debug mode will enable the interactive debugging tool, allowing ANYONE to Modified: z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/docs/example_init_domain.py Sat Mar 10 00:26:59 2007 @@ -32,6 +32,8 @@ domain_info.theme_uri = app_conf['default_theme_uri'] domain_info.set_rule_file( 'rule.xml', rule_data) - + domain_info.additional_request_headers = [ + ('X-Openplans-Project', project), + ] Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Sat Mar 10 00:26:59 2007 @@ -184,6 +184,10 @@ redirects = descriptors.json_converter( persist.file_property('redirects.txt', default=())) + additional_request_headers = descriptors.json_converter( + persist.file_property('additional_request_headers.txt'), + default=()) + def aliases__rename(self, old_aliases, new_aliases): dropped = list(old_aliases) added = list(new_aliases) @@ -273,6 +277,24 @@ optional_keys = ('path', 'prefix', 'comment') trail_slash_keys = ('prefix', 'rewrite') +class HeaderValidator(validators.FancyValidator): + header_name_re = re.compile(r'^[a-zA-Z][a-zA-Z0-9-]+$') + header_value_re = re.compile(r'^[^\n\r\000-\037\200-\377]*$') + + def validate_python(self, value, state=None): + assert isinstance(value, list), ( + "Value must be a list (not %r)" % value) + for item in list: + assert isinstance(value, (list, tuple)), ( + "Item must be a list or tuple (not %r)" % value) + assert len(item) == 2, ( + "Item must be a two-tuple (not %r)" % value) + header, value = item + assert header_name_re.search(header), ( + "Header name invalid: %r" % header) + assert header_value_re.search(value), ( + "Header value invalid: %r" % value) + class ProviderApp(server.ApplicationWrapper): theme_uri = server.Setter( @@ -285,6 +307,8 @@ validator=RemoteURIValidator()) redirects = server.JSONSetter( validator=RewriteValidator()) + additional_request_headers = server.JSONSetter( + validator=HeaderValidator()) @server.appfactory() def rules(self): Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Sat Mar 10 00:26:59 2007 @@ -1,3 +1,4 @@ +import re import posixpath import os import urlparse @@ -32,16 +33,20 @@ self.app_conf = app_conf self.provider = DataProvider(data_dir) self.rewrite_links = asbool(app_conf.get('rewrite_links', True)) + if app_conf.get('clean_environ_headers_regex'): + self.clean_environ_headers_regex = re.compile(app_conf['clean_environ_headers_regex']) + else: + self.clean_environ_headers_regex = None def __call__(self, environ, start_response): if 'paste.registry' in environ: environ['paste.registry'].register(current_environ, environ) + self.clean_environ_headers(environ) domain = environ['HTTP_HOST'] if ':' in domain: domain = domain.split(':', 1)[0] domain = self.provider.normalize(domain) domain_info = self.provider.domain(domain) - print domain_info, domain_info.initialized if not domain_info.initialized: domain_info.initialize() if self.init_domain: @@ -116,7 +121,11 @@ exc = httpexceptions.HTTPMovedPermanently( headers=[('Location', new_uri)]) return exc(environ, start_response) - + + for header_name, header_value in domain_info.additional_request_headers: + header_name = 'HTTP_%s' % header_name.upper().replace('-', '_') + environ[header_name] = header_value + for remote_uri_info in remote_uris: path = remote_uri_info['path'] if not path.endswith('/'): @@ -162,3 +171,11 @@ if os.path.exists(path): return path return None + + def clean_environ_headers(self, environ): + if not self.clean_environ_headers_regex: + return + for key in environ.keys(): + if self.clean_environ_headers_regex.search(key): + # @@: Should log this + del environ[key] From ianb at codespeak.net Sat Mar 10 00:33:28 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Sat, 10 Mar 2007 00:33:28 +0100 (CET) Subject: [z3-checkins] r40152 - z3/deliverance/DeliveranceVHoster/trunk/dvhoster Message-ID: <20070309233328.2031C10072@code0.codespeak.net> Author: ianb Date: Sat Mar 10 00:33:26 2007 New Revision: 40152 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Log: Fixed a typo and an empty file bug Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dataprovider.py Sat Mar 10 00:33:26 2007 @@ -182,11 +182,10 @@ persist.file_property('remote_uris.txt')) theme_uri = persist.file_property('theme_uri.txt') redirects = descriptors.json_converter( - persist.file_property('redirects.txt', default=())) + persist.file_property('redirects.txt', default='[]')) additional_request_headers = descriptors.json_converter( - persist.file_property('additional_request_headers.txt'), - default=()) + persist.file_property('additional_request_headers.txt', default='[]')) def aliases__rename(self, old_aliases, new_aliases): dropped = list(old_aliases) From ianb at codespeak.net Wed Mar 14 19:30:56 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 14 Mar 2007 19:30:56 +0100 (CET) Subject: [z3-checkins] r40494 - z3/deliverance/DeliveranceDemo/trunk/ddemo Message-ID: <20070314183056.4CE8310080@code0.codespeak.net> Author: ianb Date: Wed Mar 14 19:30:54 2007 New Revision: 40494 Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py Log: typo Modified: z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py (original) +++ z3/deliverance/DeliveranceDemo/trunk/ddemo/dataprovider.py Wed Mar 14 19:30:54 2007 @@ -61,7 +61,7 @@ def __init__(self, basename, strip=False): self.basename = basename - self.strip = string + self.strip = strip def filename(self, obj): return os.path.join(obj.dir, self.basename) From philikon at codespeak.net Wed Mar 14 19:31:56 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 19:31:56 +0100 (CET) Subject: [z3-checkins] r40495 - in z3/NudgeNudge: . branches tags trunk Message-ID: <20070314183156.7C59610090@code0.codespeak.net> Author: philikon Date: Wed Mar 14 19:31:54 2007 New Revision: 40495 Added: z3/NudgeNudge/ z3/NudgeNudge/branches/ z3/NudgeNudge/tags/ z3/NudgeNudge/trunk/ Log: New project NudgeNudge From philikon at codespeak.net Wed Mar 14 19:33:16 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 19:33:16 +0100 (CET) Subject: [z3-checkins] r40496 - in z3/NudgeNudge/trunk: . bootstrap src src/NudgeNudge.egg-info src/nudgenudge src/nudgenudge/app_templates src/nudgenudge/static Message-ID: <20070314183316.7D75D10090@code0.codespeak.net> Author: philikon Date: Wed Mar 14 19:33:14 2007 New Revision: 40496 Added: z3/NudgeNudge/trunk/bootstrap/ z3/NudgeNudge/trunk/bootstrap/bootstrap.py (contents, props changed) z3/NudgeNudge/trunk/buildout.cfg z3/NudgeNudge/trunk/setup.py (contents, props changed) z3/NudgeNudge/trunk/src/ z3/NudgeNudge/trunk/src/NudgeNudge.egg-info/ (props changed) z3/NudgeNudge/trunk/src/NudgeNudge.egg-info/paster_plugins.txt (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/ z3/NudgeNudge/trunk/src/nudgenudge/README.txt (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/__init__.py (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/app.py (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/app_templates/ z3/NudgeNudge/trunk/src/nudgenudge/configure.zcml (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/static/ Log: Initial project template Added: z3/NudgeNudge/trunk/bootstrap/bootstrap.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/bootstrap/bootstrap.py Wed Mar 14 19:33:14 2007 @@ -0,0 +1,52 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. + +$Id: bootstrap.py 69908 2006-08-31 21:53:00Z jim $ +""" + +import os, shutil, sys, tempfile, urllib2 + +tmpeggs = tempfile.mkdtemp() + +ez = {} +exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' + ).read() in ez +ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) + +import pkg_resources + +cmd = 'from setuptools.command.easy_install import main; main()' +if sys.platform == 'win32': + cmd = '"%s"' % cmd # work around spawn lamosity on windows + +ws = pkg_resources.working_set +assert os.spawnle( + os.P_WAIT, sys.executable, sys.executable, + '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', + dict(os.environ, + PYTHONPATH= + ws.find(pkg_resources.Requirement.parse('setuptools')).location + ), + ) == 0 + +ws.add_entry(tmpeggs) +ws.require('zc.buildout') +import zc.buildout.buildout +zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) +shutil.rmtree(tmpeggs) Added: z3/NudgeNudge/trunk/buildout.cfg ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/buildout.cfg Wed Mar 14 19:33:14 2007 @@ -0,0 +1,49 @@ +[test] +working-directory = parts/instance +eggs = nudgenudge +recipe = zc.recipe.testrunner +extra-paths = parts/zope3/src +defaults = ['--tests-pattern', '^f?tests$', + '-v' + ] + +[data] +recipe = zc.recipe.filestorage + +[zope3] +location = /usr/local/Zope-3.3.1 + +[buildout] +parts = data instance test +develop = . + +[instance] +database = data +eggs = setuptools + grok + nudgenudge +recipe = zc.recipe.zope3instance +user = grok:grok +zcml = zope.annotation + zope.copypastemove + zope.formlib + zope.i18n-meta + zope.i18n.locales + zope.publisher + zope.security-meta + zope.size + zope.traversing + zope.traversing.browser + zope.app + zope.app-meta + zope.app.securitypolicy + zope.app.securitypolicy-meta + zope.app.authentication + zope.app.catalog + zope.app.intid + zope.app.keyreference + zope.app.twisted + grok + grok-meta + nudgenudge + Added: z3/NudgeNudge/trunk/setup.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/setup.py Wed Mar 14 19:33:14 2007 @@ -0,0 +1,28 @@ +from setuptools import setup, find_packages + +version = 0.0 + +setup(name='NudgeNudge', + version=version, + description="", + long_description="""\ +""", + # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers + classifiers=[], + keywords="", + author="", + author_email="", + url="", + license="", + package_dir={'': 'src'}, + packages=find_packages('src'), + include_package_data=True, + zip_safe=False, + install_requires=['setuptools', + 'grok', + # -*- Extra requirements: -*- + ], + entry_points=""" + # -*- Entry points: -*- + """, + ) Added: z3/NudgeNudge/trunk/src/NudgeNudge.egg-info/paster_plugins.txt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/NudgeNudge.egg-info/paster_plugins.txt Wed Mar 14 19:33:14 2007 @@ -0,0 +1 @@ +PasteScript Added: z3/NudgeNudge/trunk/src/nudgenudge/README.txt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/README.txt Wed Mar 14 19:33:14 2007 @@ -0,0 +1,8 @@ +Put your application code in this Python package. + +app_templates + Place Page Templates (file extension '.pt') in this directory. They + will automatically be associated as views with the model in app.py. + +static + Place static resources such as CSS, JS, images, etc. in here. Added: z3/NudgeNudge/trunk/src/nudgenudge/__init__.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/__init__.py Wed Mar 14 19:33:14 2007 @@ -0,0 +1 @@ +# this directory is a package Added: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 19:33:14 2007 @@ -0,0 +1,5 @@ +import grok + +class NudgeNudge(grok.Application, grok.Container): + pass + Added: z3/NudgeNudge/trunk/src/nudgenudge/configure.zcml ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/configure.zcml Wed Mar 14 19:33:14 2007 @@ -0,0 +1 @@ + From ianb at codespeak.net Wed Mar 14 19:41:18 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 14 Mar 2007 19:41:18 +0100 (CET) Subject: [z3-checkins] r40497 - z3/deliverance/DeliveranceDemo/trunk Message-ID: <20070314184118.EFC2610090@code0.codespeak.net> Author: ianb Date: Wed Mar 14 19:41:15 2007 New Revision: 40497 Added: z3/deliverance/DeliveranceDemo/trunk/requirements.txt Log: Added installation requirements Added: z3/deliverance/DeliveranceDemo/trunk/requirements.txt ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceDemo/trunk/requirements.txt Wed Mar 14 19:41:15 2007 @@ -0,0 +1,6 @@ +-f http://codespeak.net/svn/z3/deliverance/trunk#egg=deliverance-dev +deliverance +-f http://svn.pythonpaste.org/Paste/HTTPEncode/trunk#egg=HTTPEncode-dev +HTTPEncode +-f http://codespeak.net/svn/z3/deliverance/DeliveranceDemo/trunk#egg=DeliveranceDemo-dev +DeliveranceDemo \ No newline at end of file From philikon at codespeak.net Wed Mar 14 19:43:51 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 19:43:51 +0100 (CET) Subject: [z3-checkins] r40498 - z3/NudgeNudge/trunk Message-ID: <20070314184351.9462010090@code0.codespeak.net> Author: philikon Date: Wed Mar 14 19:43:49 2007 New Revision: 40498 Added: z3/NudgeNudge/trunk/README.txt (contents, props changed) Log: Added project outline Added: z3/NudgeNudge/trunk/README.txt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/README.txt Wed Mar 14 19:43:49 2007 @@ -0,0 +1,32 @@ +NudgeNudge +========== + +A Python package review site + +Features +-------- + +- find reviews +- top 10 rated +- post review + rating +- similar package association +- package dependencies +- cheesecake score +- send email to package author after review submittal + +Package +------- + +- name +- dependencies +- associations + +Reviews +------- + +- score (1..10) +- title +- summary +- text +- author +- usefulness score From ianb at codespeak.net Wed Mar 14 19:45:49 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 14 Mar 2007 19:45:49 +0100 (CET) Subject: [z3-checkins] r40499 - z3/deliverance/DeliveranceDemo/trunk Message-ID: <20070314184549.7C45510090@code0.codespeak.net> Author: ianb Date: Wed Mar 14 19:45:46 2007 New Revision: 40499 Modified: z3/deliverance/DeliveranceDemo/trunk/requirements.txt Log: made ddemo editable Modified: z3/deliverance/DeliveranceDemo/trunk/requirements.txt ============================================================================== --- z3/deliverance/DeliveranceDemo/trunk/requirements.txt (original) +++ z3/deliverance/DeliveranceDemo/trunk/requirements.txt Wed Mar 14 19:45:46 2007 @@ -3,4 +3,4 @@ -f http://svn.pythonpaste.org/Paste/HTTPEncode/trunk#egg=HTTPEncode-dev HTTPEncode -f http://codespeak.net/svn/z3/deliverance/DeliveranceDemo/trunk#egg=DeliveranceDemo-dev -DeliveranceDemo \ No newline at end of file +-e DeliveranceDemo From ianb at codespeak.net Wed Mar 14 20:07:51 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 14 Mar 2007 20:07:51 +0100 (CET) Subject: [z3-checkins] r40501 - z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example Message-ID: <20070314190751.A164A10080@code0.codespeak.net> Author: ianb Date: Wed Mar 14 20:07:48 2007 New Revision: 40501 Added: z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/ z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/rule.xml z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/standardrules.xml Log: added an example rule Added: z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/rule.xml ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/rule.xml Wed Mar 14 20:07:48 2007 @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file Added: z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/standardrules.xml ============================================================================== --- (empty file) +++ z3/deliverance/DeliveranceDemo/trunk/docs/slashdot_example/standardrules.xml Wed Mar 14 20:07:48 2007 @@ -0,0 +1,9 @@ + + + + + + + + From philikon at codespeak.net Wed Mar 14 20:24:19 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 20:24:19 +0100 (CET) Subject: [z3-checkins] r40502 - in z3/NudgeNudge/trunk/src/nudgenudge: . app_templates Message-ID: <20070314192419.350C71008A@code0.codespeak.net> Author: philikon Date: Wed Mar 14 20:24:17 2007 New Revision: 40502 Added: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (contents, props changed) Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Add first views Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 20:24:17 2007 @@ -1,5 +1,36 @@ import grok +from zope import schema class NudgeNudge(grok.Application, grok.Container): - pass + pass +grok.context(NudgeNudge) + +class Index(grok.View): + + def update(self, package=None): + self.results = [] + if package: + self.results = (obj for obj in self.context.values() + if obj.package_name == package) + +class CreateReview(grok.AddForm): + form_fields = grok.Fields( + package_name = schema.TextLine(title=u'Package name'), + for_display=True # TODO not honored + ) + grok.Fields( + summary = schema.Text(title=u'Summary'), + ) + + @grok.action('Create') + def create(self, package_name, summary): + r = Review(package_name) + # TODO disambiguate naming scheme + self.context[package_name] = r + # TODO send object created event + self.redirect(self.url(r)) + +class Review(grok.Model): + + def __init__(self, package_name): + self.package_name = package_name Added: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Wed Mar 14 20:24:17 2007 @@ -0,0 +1,28 @@ + + + +
+

Find a review of...

+ +
+ + +
+
+ + + +

Write a review of...

+ +
+ + +
+ + + \ No newline at end of file From philikon at codespeak.net Wed Mar 14 20:24:29 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 20:24:29 +0100 (CET) Subject: [z3-checkins] r40503 - z3/NudgeNudge/trunk Message-ID: <20070314192429.0C4161009F@code0.codespeak.net> Author: philikon Date: Wed Mar 14 20:24:27 2007 New Revision: 40503 Modified: z3/NudgeNudge/trunk/README.txt Log: first grok nudge Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Wed Mar 14 20:24:27 2007 @@ -30,3 +30,8 @@ - text - author - usefulness score + +Grok nudges +----------- + +- grok.Fields() does not pass on for_display From philikon at codespeak.net Wed Mar 14 20:42:35 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 20:42:35 +0100 (CET) Subject: [z3-checkins] r40506 - in z3/NudgeNudge/trunk/src/nudgenudge: . review_templates Message-ID: <20070314194235.E426510088@code0.codespeak.net> Author: philikon Date: Wed Mar 14 20:42:34 2007 New Revision: 40506 Added: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/review.py (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/review_templates/ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (contents, props changed) Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Implement IReview schema, Review model and add form based on schema. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 20:42:34 2007 @@ -1,11 +1,11 @@ import grok from zope import schema +from nudgenudge.interfaces import IReview +from nudgenudge.review import Review class NudgeNudge(grok.Application, grok.Container): pass -grok.context(NudgeNudge) - class Index(grok.View): def update(self, package=None): @@ -15,22 +15,12 @@ if obj.package_name == package) class CreateReview(grok.AddForm): - form_fields = grok.Fields( - package_name = schema.TextLine(title=u'Package name'), - for_display=True # TODO not honored - ) + grok.Fields( - summary = schema.Text(title=u'Summary'), - ) - + form_fields = grok.AutoFields(IReview) # TODO make package_name read-only + @grok.action('Create') - def create(self, package_name, summary): - r = Review(package_name) + def create(self, **kw): + r = Review(**kw) # TODO disambiguate naming scheme - self.context[package_name] = r + self.context[kw['package_name']] = r # TODO send object created event self.redirect(self.url(r)) - -class Review(grok.Model): - - def __init__(self, package_name): - self.package_name = package_name Added: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py Wed Mar 14 20:42:34 2007 @@ -0,0 +1,29 @@ +from zope.interface import Interface +from zope.schema import ASCIILine, TextLine, Text, Choice + +class IReview(Interface): + + package_name = ASCIILine( + title=u'Package name', + ) + + version = ASCIILine( + title=u'Version', + ) + + summary = Text( + title=u'Summary', + ) + + text = Text( + title=u'Text' + ) + + score = Choice( + title=u'Score', + values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ) + + author = TextLine( + title=u'Author', + ) \ No newline at end of file Added: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Wed Mar 14 20:42:34 2007 @@ -0,0 +1,13 @@ +import grok +from nudgenudge.interfaces import IReview + +class Review(grok.Model): + grok.implements(IReview) + + # XXX prefer using applyChanges in the add form instead of this + def __init__(self, **kw): + for key, value in kw.items(): + setattr(self, key, value) + +class Index(grok.View): + pass Added: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 20:42:34 2007 @@ -0,0 +1,2 @@ + + \ No newline at end of file From liz at codespeak.net Wed Mar 14 21:45:30 2007 From: liz at codespeak.net (liz at codespeak.net) Date: Wed, 14 Mar 2007 21:45:30 +0100 (CET) Subject: [z3-checkins] r40507 - z3/NudgeNudge/trunk/src/nudgenudge/review_templates Message-ID: <20070314204530.472F4100A0@code0.codespeak.net> Author: liz Date: Wed Mar 14 21:45:28 2007 New Revision: 40507 Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Added review viewing page Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 21:45:28 2007 @@ -1,2 +1,42 @@ + + + + NudgeNudge Review of + <span tal:replace="context/package_name"> + [Package title] + </span> + + + + +

+ Review of + + - version + +

+ +

Author

+ + +

Score

+ + +

Summary

+
+ [Summary of the review] +
+ +

Full Review

+
+ [Full review] +
+ + + \ No newline at end of file From nando at codespeak.net Wed Mar 14 21:49:01 2007 From: nando at codespeak.net (nando at codespeak.net) Date: Wed, 14 Mar 2007 21:49:01 +0100 (CET) Subject: [z3-checkins] r40508 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070314204901.71F89100A0@code0.codespeak.net> Author: nando Date: Wed Mar 14 21:48:59 2007 New Revision: 40508 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: First searches Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 21:48:59 2007 @@ -1,18 +1,41 @@ import grok -from zope import schema +from zope import schema, component from nudgenudge.interfaces import IReview from nudgenudge.review import Review +from zope.app.intid import IntIds +from zope.app.intid.interfaces import IIntIds +from zope.app.catalog.catalog import Catalog +from zope.app.catalog.interfaces import ICatalog +from zope.app.catalog.text import TextIndex +#from zc.catalog.catalogindex import ValueIndex + + +def setup_catalog(catalog): +# catalog['fulltext'] = TextIndex('getSearchableText', ISearchableText, True) + catalog['package_name'] = TextIndex('package_name', IReview, False) +# catalog[''] = ValueIndex('package_name', IReview, False) + + class NudgeNudge(grok.Application, grok.Container): - pass + grok.local_utility(IntIds, IIntIds) # needed for the catalog + grok.local_utility(Catalog, ICatalog, setup=setup_catalog) + class Index(grok.View): def update(self, package=None): self.results = [] if package: - self.results = (obj for obj in self.context.values() - if obj.package_name == package) + # TODO seach into the catalog + + catalog = component.getUtility(ICatalog) +# query = {'any_of': (package)} + self.results = catalog.searchResults(package_name=package) + +# self.results = (obj for obj in self.context.values() +# if obj.package_name == package) + class CreateReview(grok.AddForm): form_fields = grok.AutoFields(IReview) # TODO make package_name read-only @@ -22,5 +45,10 @@ r = Review(**kw) # TODO disambiguate naming scheme self.context[kw['package_name']] = r + + # TODO insert into the catalog + grok.notify(grok.ObjectCreatedEvent(r)) + # TODO send object created event self.redirect(self.url(r)) + From tseaver at codespeak.net Wed Mar 14 21:52:01 2007 From: tseaver at codespeak.net (tseaver at codespeak.net) Date: Wed, 14 Mar 2007 21:52:01 +0100 (CET) Subject: [z3-checkins] r40509 - z3/NudgeNudge/trunk/src/nudgenudge/review_templates Message-ID: <20070314205201.77F85100A0@code0.codespeak.net> Author: tseaver Date: Wed Mar 14 21:52:00 2007 New Revision: 40509 Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Newline. Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 21:52:00 2007 @@ -39,4 +39,4 @@ - \ No newline at end of file + From hdemby at codespeak.net Wed Mar 14 21:57:37 2007 From: hdemby at codespeak.net (hdemby at codespeak.net) Date: Wed, 14 Mar 2007 21:57:37 +0100 (CET) Subject: [z3-checkins] r40510 - in z3/NudgeNudge/trunk/src/nudgenudge: . review_templates Message-ID: <20070314205737.25F91100A0@code0.codespeak.net> Author: hdemby Date: Wed Mar 14 21:57:34 2007 New Revision: 40510 Added: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py Log: started review score Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Wed Mar 14 21:57:34 2007 @@ -1,4 +1,5 @@ import grok +from BTrees.OOBTree import OOBTree from nudgenudge.interfaces import IReview class Review(grok.Model): @@ -8,6 +9,7 @@ def __init__(self, **kw): for key, value in kw.items(): setattr(self, key, value) + self.useful_scores = OOBTree() class Index(grok.View): pass Added: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt Wed Mar 14 21:57:34 2007 @@ -0,0 +1,12 @@ +

+ x + out of + y + people found this review useful. +

+ +
+ Was this review useful to you? + + +
From philikon at codespeak.net Wed Mar 14 22:04:51 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 22:04:51 +0100 (CET) Subject: [z3-checkins] r40511 - in z3/NudgeNudge/trunk: . src/nudgenudge Message-ID: <20070314210451.34F91100A0@code0.codespeak.net> Author: philikon Date: Wed Mar 14 22:04:49 2007 New Revision: 40511 Modified: z3/NudgeNudge/trunk/buildout.cfg z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Use zc.catalog's ValueIndex Modified: z3/NudgeNudge/trunk/buildout.cfg ============================================================================== --- z3/NudgeNudge/trunk/buildout.cfg (original) +++ z3/NudgeNudge/trunk/buildout.cfg Wed Mar 14 22:04:49 2007 @@ -21,6 +21,7 @@ database = data eggs = setuptools grok + zc.catalog nudgenudge recipe = zc.recipe.zope3instance user = grok:grok Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 22:04:49 2007 @@ -1,21 +1,16 @@ import grok from zope import schema, component -from nudgenudge.interfaces import IReview -from nudgenudge.review import Review - from zope.app.intid import IntIds from zope.app.intid.interfaces import IIntIds from zope.app.catalog.catalog import Catalog from zope.app.catalog.interfaces import ICatalog -from zope.app.catalog.text import TextIndex -#from zc.catalog.catalogindex import ValueIndex +from zc.catalog.catalogindex import ValueIndex +from nudgenudge.interfaces import IReview +from nudgenudge.review import Review def setup_catalog(catalog): -# catalog['fulltext'] = TextIndex('getSearchableText', ISearchableText, True) - catalog['package_name'] = TextIndex('package_name', IReview, False) -# catalog[''] = ValueIndex('package_name', IReview, False) - + catalog['package_name'] = ValueIndex('package_name', IReview, False) class NudgeNudge(grok.Application, grok.Container): grok.local_utility(IntIds, IIntIds) # needed for the catalog @@ -27,14 +22,9 @@ def update(self, package=None): self.results = [] if package: - # TODO seach into the catalog - catalog = component.getUtility(ICatalog) -# query = {'any_of': (package)} - self.results = catalog.searchResults(package_name=package) - -# self.results = (obj for obj in self.context.values() -# if obj.package_name == package) + query = {'any_of': (package,)} + self.results = catalog.searchResults(package_name=query) class CreateReview(grok.AddForm): @@ -46,9 +36,5 @@ # TODO disambiguate naming scheme self.context[kw['package_name']] = r - # TODO insert into the catalog - grok.notify(grok.ObjectCreatedEvent(r)) - - # TODO send object created event + grok.notify(grok.ObjectCreatedEvent(r)) self.redirect(self.url(r)) - From philikon at codespeak.net Wed Mar 14 22:05:14 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 22:05:14 +0100 (CET) Subject: [z3-checkins] r40512 - in z3/NudgeNudge/trunk/src/nudgenudge: . review_templates Message-ID: <20070314210514.BD304100A4@code0.codespeak.net> Author: philikon Date: Wed Mar 14 22:05:12 2007 New Revision: 40512 Removed: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Integrate review score Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Wed Mar 14 22:05:12 2007 @@ -12,4 +12,7 @@ self.useful_scores = OOBTree() class Index(grok.View): - pass + + def update(self): + self.useful_number = 0 + self.useful_total = 0 \ No newline at end of file Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 22:05:12 2007 @@ -9,6 +9,20 @@ +

+ x + out of + y + people found this review useful. +

+ +
+ Was this review useful to you? + + +
+ +

Review of Deleted: /z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt ============================================================================== --- /z3/NudgeNudge/trunk/src/nudgenudge/review_templates/viewscore.pt Wed Mar 14 22:05:12 2007 +++ (empty file) @@ -1,12 +0,0 @@ -

- x - out of - y - people found this review useful. -

- -
- Was this review useful to you? - - -
From philikon at codespeak.net Wed Mar 14 22:21:34 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 22:21:34 +0100 (CET) Subject: [z3-checkins] r40514 - in z3/NudgeNudge/trunk: . src/nudgenudge src/nudgenudge/review_templates Message-ID: <20070314212134.AAB63100A5@code0.codespeak.net> Author: philikon Date: Wed Mar 14 22:21:26 2007 New Revision: 40514 Modified: z3/NudgeNudge/trunk/buildout.cfg z3/NudgeNudge/trunk/src/nudgenudge/review.py z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Implement the scoring system, needs client id manager utility from zope.app.session. Modified: z3/NudgeNudge/trunk/buildout.cfg ============================================================================== --- z3/NudgeNudge/trunk/buildout.cfg (original) +++ z3/NudgeNudge/trunk/buildout.cfg Wed Mar 14 22:21:26 2007 @@ -43,6 +43,7 @@ zope.app.catalog zope.app.intid zope.app.keyreference + zope.app.session zope.app.twisted grok grok-meta Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Wed Mar 14 22:21:26 2007 @@ -1,12 +1,14 @@ import grok from BTrees.OOBTree import OOBTree +from zope.component import getUtility +from zope.app.session.interfaces import IClientIdManager from nudgenudge.interfaces import IReview class Review(grok.Model): grok.implements(IReview) - # XXX prefer using applyChanges in the add form instead of this def __init__(self, **kw): + # XXX prefer using applyChanges in the add form instead of this for key, value in kw.items(): setattr(self, key, value) self.useful_scores = OOBTree() @@ -14,5 +16,17 @@ class Index(grok.View): def update(self): - self.useful_number = 0 - self.useful_total = 0 \ No newline at end of file + self.useful_number = len([score for score in + self.context.useful_scores.values() if score]) + self.useful_total = len(self.context.useful_scores) + client_id = getUtility(IClientIdManager).getClientId(self.request) + self.show_score_form = client_id not in self.context.useful_scores + +class Score(grok.View): + + def render(self, score): + score = score.lower() == 'yes' + client_id = getUtility(IClientIdManager).getClientId(self.request) + if client_id not in self.context.useful_scores: + self.context.useful_scores[client_id] = score + self.redirect('index') \ No newline at end of file Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 22:21:26 2007 @@ -16,10 +16,10 @@ people found this review useful.

-
+ Was this review useful to you? - - + +
From philikon at codespeak.net Wed Mar 14 22:27:44 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 22:27:44 +0100 (CET) Subject: [z3-checkins] r40515 - z3/NudgeNudge/trunk Message-ID: <20070314212744.63D8A100A5@code0.codespeak.net> Author: philikon Date: Wed Mar 14 22:27:40 2007 New Revision: 40515 Modified: z3/NudgeNudge/trunk/README.txt Log: add more features and nudges Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Wed Mar 14 22:27:40 2007 @@ -13,13 +13,7 @@ - package dependencies - cheesecake score - send email to package author after review submittal - -Package -------- - -- name -- dependencies -- associations +- list all reviews by one author Reviews ------- @@ -34,4 +28,6 @@ Grok nudges ----------- -- grok.Fields() does not pass on for_display +- grok.Fields() does not pass on for_display, AutoFields() doesn't take it either +- grok.AddForm doesn't have applyChanges +- can't override form template From philikon at codespeak.net Wed Mar 14 22:27:53 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Wed, 14 Mar 2007 22:27:53 +0100 (CET) Subject: [z3-checkins] r40516 - z3/NudgeNudge/trunk/src/nudgenudge/app_templates Message-ID: <20070314212753.6F71A100AA@code0.codespeak.net> Author: philikon Date: Wed Mar 14 22:27:50 2007 New Revision: 40516 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Log: nicer result page Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Wed Mar 14 22:27:50 2007 @@ -12,8 +12,10 @@ From tseaver at codespeak.net Wed Mar 14 22:30:04 2007 From: tseaver at codespeak.net (tseaver at codespeak.net) Date: Wed, 14 Mar 2007 22:30:04 +0100 (CET) Subject: [z3-checkins] r40517 - in z3/NudgeNudge/trunk/src/nudgenudge: app_templates review_templates static Message-ID: <20070314213004.E36B6100A5@code0.codespeak.net> Author: tseaver Date: Wed Mar 14 22:29:59 2007 New Revision: 40517 Added: z3/NudgeNudge/trunk/src/nudgenudge/static/style.css Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: - Add some style. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Wed Mar 14 22:29:59 2007 @@ -1,4 +1,8 @@ + + +
@@ -27,4 +31,4 @@ - \ No newline at end of file + Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Wed Mar 14 22:29:59 2007 @@ -7,6 +7,8 @@ [Package title] +

Added: z3/NudgeNudge/trunk/src/nudgenudge/static/style.css ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/style.css Wed Mar 14 22:29:59 2007 @@ -0,0 +1,20 @@ +body { + font-family: serif; +} + +h1, h2, h3, h4 { + font-family: sans-serif; + background-color: #448844; + color: White; + padding: 0.3em; +} + +fieldset { + background-color: #E8FFE8; + margin-bottom: 1em; +} + +legend { + font-weight: bold; + color: #448844; +} From ianb at codespeak.net Wed Mar 14 22:33:42 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Wed, 14 Mar 2007 22:33:42 +0100 (CET) Subject: [z3-checkins] r40518 - z3/deliverance/trunk/deliverance Message-ID: <20070314213342.8872A100A5@code0.codespeak.net> Author: ianb Date: Wed Mar 14 22:33:25 2007 New Revision: 40518 Modified: z3/deliverance/trunk/deliverance/resource_fetcher.py Log: Do proper WSGI unquoting of PATH_INFO, SCRIPT_NAME Modified: z3/deliverance/trunk/deliverance/resource_fetcher.py ============================================================================== --- z3/deliverance/trunk/deliverance/resource_fetcher.py (original) +++ z3/deliverance/trunk/deliverance/resource_fetcher.py Wed Mar 14 22:33:25 2007 @@ -1,3 +1,4 @@ +import urllib import deliverance.wsgimiddleware from StringIO import StringIO from paste.wsgilib import intercept_output @@ -25,7 +26,7 @@ uri_parts = urlparse.urlparse(uri) - self.environ['PATH_INFO'] = uri_parts[2] + self.environ['PATH_INFO'] = urllib.unquote(uri_parts[2]) if len(uri_parts[4]) > 0: self.environ['QUERY_STRING'] = uri_parts[4] + '¬heme' else: @@ -33,7 +34,7 @@ base_url = in_environ['deliverance.base-url'] if base_url is not None: - self.environ['SCRIPT_NAME'] = urlparse.urlparse(base_url)[2] + self.environ['SCRIPT_NAME'] = urllib.unquote(urlparse.urlparse(base_url)[2]) else: self.environ['SCRIPT_NAME'] = '' From tseaver at codespeak.net Wed Mar 14 22:38:26 2007 From: tseaver at codespeak.net (tseaver at codespeak.net) Date: Wed, 14 Mar 2007 22:38:26 +0100 (CET) Subject: [z3-checkins] r40519 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070314213826.BA36810072@code0.codespeak.net> Author: tseaver Date: Wed Mar 14 22:38:13 2007 New Revision: 40519 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: - Prevent duplicate IDs. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Wed Mar 14 22:38:13 2007 @@ -18,23 +18,27 @@ class Index(grok.View): - - def update(self, package=None): - self.results = [] - if package: - catalog = component.getUtility(ICatalog) - query = {'any_of': (package,)} - self.results = catalog.searchResults(package_name=query) + + def update(self, package=None): + self.results = [] + if package: + catalog = component.getUtility(ICatalog) + query = {'any_of': (package,)} + self.results = catalog.searchResults(package_name=query) class CreateReview(grok.AddForm): - form_fields = grok.AutoFields(IReview) # TODO make package_name read-only + form_fields = grok.AutoFields(IReview) # TODO make package_name read-only - @grok.action('Create') - def create(self, **kw): - r = Review(**kw) - # TODO disambiguate naming scheme - self.context[kw['package_name']] = r + @grok.action('Create') + def create(self, **kw): + r = Review(**kw) + base = name = kw['package_name'] + count = 0 + while name in self.context: + count += 1 + name = '%s_%03d' % (base, count) + self.context[name] = r - grok.notify(grok.ObjectCreatedEvent(r)) - self.redirect(self.url(r)) + grok.notify(grok.ObjectCreatedEvent(r)) + self.redirect(self.url(r)) From philikon at codespeak.net Thu Mar 15 07:10:34 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 07:10:34 +0100 (CET) Subject: [z3-checkins] r40522 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070315061034.D4BA710072@code0.codespeak.net> Author: philikon Date: Thu Mar 15 07:10:31 2007 New Revision: 40522 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Include version in object name Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Thu Mar 15 07:10:31 2007 @@ -33,7 +33,7 @@ @grok.action('Create') def create(self, **kw): r = Review(**kw) - base = name = kw['package_name'] + base = name = kw['package_name'] + '-' + kw['version'] count = 0 while name in self.context: count += 1 From philikon at codespeak.net Thu Mar 15 07:10:52 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 07:10:52 +0100 (CET) Subject: [z3-checkins] r40523 - z3/NudgeNudge/trunk Message-ID: <20070315061052.E17B010087@code0.codespeak.net> Author: philikon Date: Thu Mar 15 07:10:50 2007 New Revision: 40523 Modified: z3/NudgeNudge/trunk/README.txt Log: reorder / add some features Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Thu Mar 15 07:10:50 2007 @@ -7,13 +7,15 @@ -------- - find reviews -- top 10 rated -- post review + rating -- similar package association -- package dependencies +- top 10 rated packages - cheesecake score - send email to package author after review submittal - list all reviews by one author +- latest reviews, RSS + +- similar package association? +- package dependencies? + Reviews ------- From philikon at codespeak.net Thu Mar 15 07:13:51 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 07:13:51 +0100 (CET) Subject: [z3-checkins] r40524 - z3/NudgeNudge/trunk Message-ID: <20070315061351.70AE7100A5@code0.codespeak.net> Author: philikon Date: Thu Mar 15 07:13:49 2007 New Revision: 40524 Modified: z3/NudgeNudge/trunk/ (props changed) Log: ignore buildout stuff From chrism at codespeak.net Thu Mar 15 15:04:13 2007 From: chrism at codespeak.net (chrism at codespeak.net) Date: Thu, 15 Mar 2007 15:04:13 +0100 (CET) Subject: [z3-checkins] r40532 - in z3/deliverance/zdeliverance: . tests Message-ID: <20070315140413.4E145100AA@code0.codespeak.net> Author: chrism Date: Thu Mar 15 15:04:11 2007 New Revision: 40532 Added: z3/deliverance/zdeliverance/ z3/deliverance/zdeliverance/__init__.py (contents, props changed) z3/deliverance/zdeliverance/tests/ z3/deliverance/zdeliverance/tests/__init__.py (contents, props changed) z3/deliverance/zdeliverance/tests/test_traversal.py (contents, props changed) z3/deliverance/zdeliverance/traversal.py (contents, props changed) Log: Import nascent "zdeliverance" package. Added: z3/deliverance/zdeliverance/__init__.py ============================================================================== --- (empty file) +++ z3/deliverance/zdeliverance/__init__.py Thu Mar 15 15:04:11 2007 @@ -0,0 +1 @@ +# this is a package Added: z3/deliverance/zdeliverance/tests/__init__.py ============================================================================== --- (empty file) +++ z3/deliverance/zdeliverance/tests/__init__.py Thu Mar 15 15:04:11 2007 @@ -0,0 +1 @@ +# this is a package Added: z3/deliverance/zdeliverance/tests/test_traversal.py ============================================================================== --- (empty file) +++ z3/deliverance/zdeliverance/tests/test_traversal.py Thu Mar 15 15:04:11 2007 @@ -0,0 +1,72 @@ +import unittest +import time + +from zope.app.testing import ztapi +from zope.app.testing.placelesssetup import PlacelessSetup +from zope.interface.verify import verifyClass + +class DeliveranceRuleTests(unittest.TestCase): + def _getTargetClass(self): + from Products.zdeliverance.traversal import DeliveranceRule + return DeliveranceRule + + def _makeOne(self, id='therule'): + return self._getTargetClass()(id) + + def test_manage_afterAdd(self): + rule = self._makeOne() + item = Dummy() + container = Dummy() + rule.manage_afterAdd(item, container) + from ZPublisher.BeforeTraverse import queryBeforeTraverse + self.assertEqual(queryBeforeTraverse(container, 'Deliverance'), + [(1, rule)]) + + def test_manage_beforeDelete(self): + rule = self._makeOne() + item = Dummy() + container = Dummy() + from ZPublisher.BeforeTraverse import registerBeforeTraverse + registerBeforeTraverse(container, None, 'Deliverance', 1) + rule.manage_beforeDelete(item, container) + from ZPublisher.BeforeTraverse import queryBeforeTraverse + self.assertEqual(queryBeforeTraverse(container, 'Deliverance'), []) + + def test___call__(self): + rule = self._makeOne() + container = Dummy() + request = DummyRequest() + rule.theme_url = 'the theme url' + rule.rule = 'the rule' + rule.__call__(container, request) + response = request.RESPONSE + response.setBody('hello') + self.assertEqual(response.body, 'hello') + self.assertEqual(request.data['DELIVERANCE_THEME'], + {'theme_url':'the theme url', + 'rule':'the rule'}) + +# puru1pip + +class Dummy: + pass + +class DummyResponse: + def setBody(self, body, title): + self.body = body + +class DummyRequest: + def __init__(self): + self.data = {} + self.RESPONSE = DummyResponse() + + def set(self, key, value): + self.data[key] = value + +def test_suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(DeliveranceRuleTests)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') Added: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- (empty file) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 15:04:11 2007 @@ -0,0 +1,53 @@ +from ZPublisher.BeforeTraverse import registerBeforeTraverse +from ZPublisher.BeforeTraverse import unregisterBeforeTraverse +from ZPublisher.BeforeTraverse import queryBeforeTraverse + +from OFS.SimpleItem import SimpleItem +from OFS.PropertyManager import PropertyManager + +class DeliveranceRule(SimpleItem, PropertyManager): + """ An object which invokes a deliverance rule when its container + is traversed """ + + meta_type = 'Deliverance Rule' + theme_url = '' + rule = '' + + _properties = ( + {'id':'title', 'type':'string', 'mode':'w'}, + {'id':'theme_url', 'type':'string', 'mode':'w', 'label':'Theme URL'}, + {'id':'rule', 'type':'text', 'mode':'w', 'label':'Rule'}, + ) + + def __init__(self, id): + self.id = id + + def manage_afterAdd(self, item, container): + existing = queryBeforeTraverse(container, 'Deliverance') + if existing: + raise ValueError('A Deliverance Rule named %r is already in ' + 'this container' % existing[0][0]) + registerBeforeTraverse(container, self, 'Deliverance', 1) + + def manage_beforeDelete(self, item, container): + unregisterBeforeTraverse(container, 'Deliverance') + + def __call__(self, container, request): + response = request.RESPONSE + orig_setBody = response.setBody + def setBody(body, title=''): + orig_setBody(body, title) + # XXX do work + orig_setBody(body, title) + response.setBody = setBody + request.set('DELIVERANCE_THEME', {'theme_url':self.theme_url, + 'rule':self.rule}) + +def manage_addDeliveranceRule(self, id, REQUEST=None): + """ Add a deliverance rule """ + rule = DeliveranceRule(id) + self._setObject(id, rule) + rule_url = rule.absolute_url() + '/manage_main' + if REQUEST is not None: + REQUEST.RESPONSE.redirect(rule_url) + From chrism at codespeak.net Thu Mar 15 15:26:27 2007 From: chrism at codespeak.net (chrism at codespeak.net) Date: Thu, 15 Mar 2007 15:26:27 +0100 (CET) Subject: [z3-checkins] r40535 - in z3/deliverance/zdeliverance: . tests www Message-ID: <20070315142627.9BF551008A@code0.codespeak.net> Author: chrism Date: Thu Mar 15 15:26:22 2007 New Revision: 40535 Added: z3/deliverance/zdeliverance/www/ z3/deliverance/zdeliverance/www/rule.gif (contents, props changed) Modified: z3/deliverance/zdeliverance/__init__.py z3/deliverance/zdeliverance/tests/test_traversal.py z3/deliverance/zdeliverance/traversal.py Log: Make rules addable via ZMI. Modified: z3/deliverance/zdeliverance/__init__.py ============================================================================== --- z3/deliverance/zdeliverance/__init__.py (original) +++ z3/deliverance/zdeliverance/__init__.py Thu Mar 15 15:26:22 2007 @@ -1 +1,12 @@ -# this is a package +from Products.zdeliverance.traversal import DeliveranceRule +from Products.zdeliverance.traversal import manage_addDeliveranceRule + +def initialize(context): + context.registerClass( + DeliveranceRule, + icon="www/rule.gif", + permission='Add Deliverance Rules', + constructors=(manage_addDeliveranceRule,), + ) + + Modified: z3/deliverance/zdeliverance/tests/test_traversal.py ============================================================================== --- z3/deliverance/zdeliverance/tests/test_traversal.py (original) +++ z3/deliverance/zdeliverance/tests/test_traversal.py Thu Mar 15 15:26:22 2007 @@ -10,8 +10,8 @@ from Products.zdeliverance.traversal import DeliveranceRule return DeliveranceRule - def _makeOne(self, id='therule'): - return self._getTargetClass()(id) + def _makeOne(self): + return self._getTargetClass()() def test_manage_afterAdd(self): rule = self._makeOne() @@ -52,8 +52,8 @@ pass class DummyResponse: - def setBody(self, body, title): - self.body = body + def setBody(self, *arg, **kw): + self.body = arg[0] class DummyRequest: def __init__(self): Modified: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- z3/deliverance/zdeliverance/traversal.py (original) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 15:26:22 2007 @@ -12,16 +12,15 @@ meta_type = 'Deliverance Rule' theme_url = '' rule = '' + id = 'deliverance_rule' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'theme_url', 'type':'string', 'mode':'w', 'label':'Theme URL'}, {'id':'rule', 'type':'text', 'mode':'w', 'label':'Rule'}, ) + manage_options = PropertyManager.manage_options - def __init__(self, id): - self.id = id - def manage_afterAdd(self, item, container): existing = queryBeforeTraverse(container, 'Deliverance') if existing: @@ -35,18 +34,18 @@ def __call__(self, container, request): response = request.RESPONSE orig_setBody = response.setBody - def setBody(body, title=''): - orig_setBody(body, title) + def setBody(*arg, **kw): + orig_setBody(*arg, **kw) # XXX do work - orig_setBody(body, title) + orig_setBody(*arg, **kw) response.setBody = setBody request.set('DELIVERANCE_THEME', {'theme_url':self.theme_url, 'rule':self.rule}) -def manage_addDeliveranceRule(self, id, REQUEST=None): +def manage_addDeliveranceRule(self, REQUEST=None): """ Add a deliverance rule """ - rule = DeliveranceRule(id) - self._setObject(id, rule) + rule = DeliveranceRule() + self._setObject(rule.getId(), rule) rule_url = rule.absolute_url() + '/manage_main' if REQUEST is not None: REQUEST.RESPONSE.redirect(rule_url) Added: z3/deliverance/zdeliverance/www/rule.gif ============================================================================== Binary file. No diff available. From philikon at codespeak.net Thu Mar 15 15:28:30 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 15:28:30 +0100 (CET) Subject: [z3-checkins] r40536 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070315142830.72EC61008A@code0.codespeak.net> Author: philikon Date: Thu Mar 15 15:28:24 2007 New Revision: 40536 Modified: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py z3/NudgeNudge/trunk/src/nudgenudge/review.py Log: M-x untabify Modified: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py Thu Mar 15 15:28:24 2007 @@ -2,28 +2,28 @@ from zope.schema import ASCIILine, TextLine, Text, Choice class IReview(Interface): - - package_name = ASCIILine( - title=u'Package name', - ) - - version = ASCIILine( - title=u'Version', - ) - - summary = Text( - title=u'Summary', - ) - - text = Text( - title=u'Text' - ) - - score = Choice( - title=u'Score', - values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ) + + package_name = ASCIILine( + title=u'Package name', + ) + + version = ASCIILine( + title=u'Version', + ) + + summary = Text( + title=u'Summary', + ) + + text = Text( + title=u'Text' + ) + + score = Choice( + title=u'Score', + values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + ) - author = TextLine( - title=u'Author', - ) \ No newline at end of file + author = TextLine( + title=u'Author', + ) Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Thu Mar 15 15:28:24 2007 @@ -5,28 +5,28 @@ from nudgenudge.interfaces import IReview class Review(grok.Model): - grok.implements(IReview) + grok.implements(IReview) - def __init__(self, **kw): - # XXX prefer using applyChanges in the add form instead of this - for key, value in kw.items(): - setattr(self, key, value) - self.useful_scores = OOBTree() + def __init__(self, **kw): + # XXX prefer using applyChanges in the add form instead of this + for key, value in kw.items(): + setattr(self, key, value) + self.useful_scores = OOBTree() class Index(grok.View): - - def update(self): - self.useful_number = len([score for score in - self.context.useful_scores.values() if score]) - self.useful_total = len(self.context.useful_scores) - client_id = getUtility(IClientIdManager).getClientId(self.request) - self.show_score_form = client_id not in self.context.useful_scores + + def update(self): + self.useful_number = len([score for score in + self.context.useful_scores.values() if score]) + self.useful_total = len(self.context.useful_scores) + client_id = getUtility(IClientIdManager).getClientId(self.request) + self.show_score_form = client_id not in self.context.useful_scores class Score(grok.View): - - def render(self, score): - score = score.lower() == 'yes' - client_id = getUtility(IClientIdManager).getClientId(self.request) - if client_id not in self.context.useful_scores: - self.context.useful_scores[client_id] = score - self.redirect('index') \ No newline at end of file + + def render(self, score): + score = score.lower() == 'yes' + client_id = getUtility(IClientIdManager).getClientId(self.request) + if client_id not in self.context.useful_scores: + self.context.useful_scores[client_id] = score + self.redirect('index') From ianb at codespeak.net Thu Mar 15 16:24:48 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 15 Mar 2007 16:24:48 +0100 (CET) Subject: [z3-checkins] r40540 - in z3/deliverance/zdeliverance: . tests Message-ID: <20070315152448.1665F10094@code0.codespeak.net> Author: ianb Date: Thu Mar 15 16:24:47 2007 New Revision: 40540 Modified: z3/deliverance/zdeliverance/tests/test_traversal.py z3/deliverance/zdeliverance/traversal.py Log: Added deliverance tranformations Modified: z3/deliverance/zdeliverance/tests/test_traversal.py ============================================================================== --- z3/deliverance/zdeliverance/tests/test_traversal.py (original) +++ z3/deliverance/zdeliverance/tests/test_traversal.py Thu Mar 15 16:24:47 2007 @@ -1,5 +1,6 @@ import unittest import time +import re from zope.app.testing import ztapi from zope.app.testing.placelesssetup import PlacelessSetup @@ -36,17 +37,31 @@ rule = self._makeOne() container = Dummy() request = DummyRequest() - rule.theme_url = 'the theme url' - rule.rule = 'the rule' + rule.theme_uri = 'data:' + rule.rule = 'the rule' rule.__call__(container, request) response = request.RESPONSE + response.headers = {'content-type': 'text/plain'} response.setBody('hello') self.assertEqual(response.body, 'hello') - self.assertEqual(request.data['DELIVERANCE_THEME'], - {'theme_url':'the theme url', - 'rule':'the rule'}) -# puru1pip + def test_transform(self): + rule = self._makeOne() + container = Dummy() + request = DummyRequest() + rule.theme_uri = '''data: +

Example

''' + rule.rule = ''' + + ''' + rule.__call__(container, request) + response = request.RESPONSE + response.headers = {'content-type': 'text/html'} + response.setBody(''' +
some content
''') + body = re.sub(r'[\n\t]', '', response.body) + self.assertEqual(body, '''

Example

some content
''') + class Dummy: pass Modified: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- z3/deliverance/zdeliverance/traversal.py (original) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 16:24:47 2007 @@ -5,6 +5,11 @@ from OFS.SimpleItem import SimpleItem from OFS.PropertyManager import PropertyManager +from deliverance.interpreter import Renderer +from deliverance import htmlserialize +from lxml import etree +import urllib + class DeliveranceRule(SimpleItem, PropertyManager): """ An object which invokes a deliverance rule when its container is traversed """ @@ -16,7 +21,7 @@ _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, - {'id':'theme_url', 'type':'string', 'mode':'w', 'label':'Theme URL'}, + {'id':'theme_uri', 'type':'string', 'mode':'w', 'label':'Theme URI'}, {'id':'rule', 'type':'text', 'mode':'w', 'label':'Rule'}, ) manage_options = PropertyManager.manage_options @@ -36,12 +41,57 @@ orig_setBody = response.setBody def setBody(*arg, **kw): orig_setBody(*arg, **kw) - # XXX do work - orig_setBody(*arg, **kw) + # Should also stop if deliverance.theme doesn't match up to self + if not response.headers['content-type'].startswith('text/html'): + return response + body = self.transform_body(response) + return orig_setBody(body) response.setBody = setBody - request.set('DELIVERANCE_THEME', {'theme_url':self.theme_url, - 'rule':self.rule}) - + + def transform_body(self, response): + interp = self.make_renderer() + body = response.body + transformed = interp.render(etree.HTML(body)) + return htmlserialize.tostring(transformed) + + def make_renderer(self): + return Renderer( + theme=self.get_theme(), + theme_uri=self.theme_uri, + rule=self.get_rule(), + rule_uri=self.get_rule_uri(), + reference_resolver=self.resolve_reference) + + def get_theme(self): + return self.resolve_reference(self.theme_uri, 'html') + + def get_rule(self): + return etree.XML(self.rule) + + def get_rule_uri(self): + self.absolute_url() + '/rule' + + def resolve_reference(self, href, parse, encoding=None): + text = self.get_resource(href) + if parse == "xml": + return etree.XML(text) + if parse == "html": + return etree.HTML(text) + else: + if encoding: + return text.decode(encoding) + else: + return text + + def get_resource(self, href): + if href.startswith('data:'): + return href[5:] + # @@: This is a really bad implementation + f = urllib.urlopen(href) + c = f.read() + f.close() + return f + def manage_addDeliveranceRule(self, REQUEST=None): """ Add a deliverance rule """ rule = DeliveranceRule() From ianb at codespeak.net Thu Mar 15 16:35:53 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 15 Mar 2007 16:35:53 +0100 (CET) Subject: [z3-checkins] r40541 - z3/deliverance/zdeliverance Message-ID: <20070315153553.00C3B10094@code0.codespeak.net> Author: ianb Date: Thu Mar 15 16:35:52 2007 New Revision: 40541 Modified: z3/deliverance/zdeliverance/traversal.py Log: Fix some bugs found during interactive test Modified: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- z3/deliverance/zdeliverance/traversal.py (original) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 16:35:52 2007 @@ -15,7 +15,7 @@ is traversed """ meta_type = 'Deliverance Rule' - theme_url = '' + theme_uri = '' rule = '' id = 'deliverance_rule' @@ -42,7 +42,8 @@ def setBody(*arg, **kw): orig_setBody(*arg, **kw) # Should also stop if deliverance.theme doesn't match up to self - if not response.headers['content-type'].startswith('text/html'): + if (not response.headers.get('content-type', '').startswith('text/html') + or not self.rule): return response body = self.transform_body(response) return orig_setBody(body) @@ -90,7 +91,7 @@ f = urllib.urlopen(href) c = f.read() f.close() - return f + return c def manage_addDeliveranceRule(self, REQUEST=None): """ Add a deliverance rule """ From nando at codespeak.net Thu Mar 15 16:49:10 2007 From: nando at codespeak.net (nando at codespeak.net) Date: Thu, 15 Mar 2007 16:49:10 +0100 (CET) Subject: [z3-checkins] r40543 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070315154910.D504B10094@code0.codespeak.net> Author: nando Date: Thu Mar 15 16:49:07 2007 New Revision: 40543 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Started Sign Up functionality Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Thu Mar 15 16:49:07 2007 @@ -4,18 +4,40 @@ from zope.app.intid.interfaces import IIntIds from zope.app.catalog.catalog import Catalog from zope.app.catalog.interfaces import ICatalog +from zope.app.authentication import PluggableAuthentication +from zope.app.security.interfaces import IAuthentication from zc.catalog.catalogindex import ValueIndex +from zope.app.authentication.groupfolder import GroupFolder, GroupInformation +from zope.app.authentication.principalfolder import PrincipalFolder +from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager +from zope.annotation.interfaces import IAttributeAnnotatable from nudgenudge.interfaces import IReview from nudgenudge.review import Review +grok.define_permission('nudge.AddReview') +grok.define_permission('nudge.EditReview') +grok.define_permission('nudge.DeleteReview') + def setup_catalog(catalog): catalog['package_name'] = ValueIndex('package_name', IReview, False) +def setup_pau(pau): + pau['groups'] = groups = GroupFolder('nudge.groups.') + groups['reviewers'] = GroupInformation('Nudge Reviewers') + pau['principals'] = principals = PrincipalFolder('nudge.principals.') + pau.credentialPlugins = ('groups', 'principals',) + class NudgeNudge(grok.Application, grok.Container): grok.local_utility(IntIds, IIntIds) # needed for the catalog grok.local_utility(Catalog, ICatalog, setup=setup_catalog) + grok.local_utility(PluggableAuthentication, IAuthentication, setup=setup_pau) + grok.implements(IAttributeAnnotatable) + at grok.subscribe(NudgeNudge, grok.IObjectAddedEvent) +def set_permissions(app, event): + role_manager = IPrincipalPermissionManager(app) + role_manager.grantPermissionToPrincipal('nudge.AddReview', 'nudge.groups.reviewers') class Index(grok.View): @@ -28,6 +50,8 @@ class CreateReview(grok.AddForm): + grok.require('nudge.AddReview') + form_fields = grok.AutoFields(IReview) # TODO make package_name read-only @grok.action('Create') @@ -42,3 +66,20 @@ grok.notify(grok.ObjectCreatedEvent(r)) self.redirect(self.url(r)) + +class SignUp(grok.EditForm): # XXX Not really an edit form + + form_fields = grok.Fields( + name = schema.TextLine(title=u"Your name"), + email = schema.TextLine(title=u"Email address"), # TODO: validate email address + password = schema.Password(title=u"Password"), + password_repeat = schema.Password(title=u"Repeat password"), + ) + + @grok.action('Sign up') + def sign_up(self, name, email, password, password_repeat): + # TODO: validate password is equal to password_repeat + pau = component.getUtility(IAuthentication) + pau['principals'][email] = user = InternalPrincipal(email, password, name) + pau['groups']['reviewers'].principals += [user] + self.redirect('index') From ianb at codespeak.net Thu Mar 15 17:00:13 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 15 Mar 2007 17:00:13 +0100 (CET) Subject: [z3-checkins] r40545 - z3/deliverance/zdeliverance Message-ID: <20070315160013.38C0610091@code0.codespeak.net> Author: ianb Date: Thu Mar 15 17:00:08 2007 New Revision: 40545 Modified: z3/deliverance/zdeliverance/traversal.py Log: Fixed encoding issues Modified: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- z3/deliverance/zdeliverance/traversal.py (original) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 17:00:08 2007 @@ -9,6 +9,7 @@ from deliverance import htmlserialize from lxml import etree import urllib +import re class DeliveranceRule(SimpleItem, PropertyManager): """ An object which invokes a deliverance rule when its container @@ -17,12 +18,14 @@ meta_type = 'Deliverance Rule' theme_uri = '' rule = '' + transform_ports = () id = 'deliverance_rule' _properties = ( {'id':'title', 'type':'string', 'mode':'w'}, {'id':'theme_uri', 'type':'string', 'mode':'w', 'label':'Theme URI'}, {'id':'rule', 'type':'text', 'mode':'w', 'label':'Rule'}, + {'id':'transform_ports', 'type':'lines', 'mode':'w', 'label':'Transform Ports'}, ) manage_options = PropertyManager.manage_options @@ -38,12 +41,16 @@ def __call__(self, container, request): response = request.RESPONSE + port = request.SERVER_PORT + if self.transform_ports and port not in self.transform_ports: + return + if not self.rule: + # Hasn't been initialized + return orig_setBody = response.setBody def setBody(*arg, **kw): orig_setBody(*arg, **kw) - # Should also stop if deliverance.theme doesn't match up to self - if (not response.headers.get('content-type', '').startswith('text/html') - or not self.rule): + if not response.headers.get('content-type', '').startswith('text/html'): return response body = self.transform_body(response) return orig_setBody(body) @@ -52,6 +59,16 @@ def transform_body(self, response): interp = self.make_renderer() body = response.body + content_type = response.headers['content-type'] + match = self._meta_charset_re.search(body) + if match: + body = body.decode(match.group(1), 'ignore') + else: + match = self._charset_re.search(content_type) + if match: + body = body.decode(match.group(1), 'ignore') + content_type = content_type.split(';')[0] + '; charset=UTF-8' + response.headers['content-type'] = content_type transformed = interp.render(etree.HTML(body)) return htmlserialize.tostring(transformed) @@ -84,13 +101,25 @@ else: return text + _charset_re = re.compile(r'charset=([a-z0-9_-]+)', re.I) + _meta_charset_re = re.compile(r']*charset=([a-z0-9_-]+).*?>', re.I) + def get_resource(self, href): if href.startswith('data:'): return href[5:] # @@: This is a really bad implementation f = urllib.urlopen(href) c = f.read() - f.close() + match = self._meta_charset_re.search(c) + if match: + c = c.decode(match.group(1), 'ignore') + else: + content_type = f.info().get('Content-Type') + f.close() + if content_type: + match = self._charset_re.search(content_type) + if match: + c = c.decode(match.group(1), 'ignore') return c def manage_addDeliveranceRule(self, REQUEST=None): From ianb at codespeak.net Thu Mar 15 17:41:54 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 15 Mar 2007 17:41:54 +0100 (CET) Subject: [z3-checkins] r40549 - z3/deliverance/zdeliverance Message-ID: <20070315164154.53BEA100A5@code0.codespeak.net> Author: ianb Date: Thu Mar 15 17:41:52 2007 New Revision: 40549 Modified: z3/deliverance/zdeliverance/traversal.py Log: use urllib2 instead of urllib; set Accept: html Modified: z3/deliverance/zdeliverance/traversal.py ============================================================================== --- z3/deliverance/zdeliverance/traversal.py (original) +++ z3/deliverance/zdeliverance/traversal.py Thu Mar 15 17:41:52 2007 @@ -8,7 +8,7 @@ from deliverance.interpreter import Renderer from deliverance import htmlserialize from lxml import etree -import urllib +import urllib2 import re class DeliveranceRule(SimpleItem, PropertyManager): @@ -108,7 +108,11 @@ if href.startswith('data:'): return href[5:] # @@: This is a really bad implementation - f = urllib.urlopen(href) + req = urllib2.Request( + href, headers={ + 'Accept': 'text/html,application/xhtml+xml'}, + unverifiable=True) + f = urllib2.urlopen(req) c = f.read() match = self._meta_charset_re.search(c) if match: From philikon at codespeak.net Thu Mar 15 17:43:56 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 17:43:56 +0100 (CET) Subject: [z3-checkins] r40550 - z3/NudgeNudge/trunk Message-ID: <20070315164356.1655E10036@code0.codespeak.net> Author: philikon Date: Thu Mar 15 17:43:55 2007 New Revision: 40550 Modified: z3/NudgeNudge/trunk/README.txt Log: udpate Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Thu Mar 15 17:43:55 2007 @@ -1,12 +1,24 @@ NudgeNudge ========== -A Python package review site +A Python package review site. -Features --------- +Goals +----- + +* Visitors can sign up become reviewers +* Reviewers can write reviews of Python packages +* Visitors can search for reviews + +Non-Goals +--------- + +* Store any information that's already in PyPI (basically, store no + information about packages, just reviews) + +Possible Features +----------------- -- find reviews - top 10 rated packages - cheesecake score - send email to package author after review submittal @@ -16,20 +28,12 @@ - similar package association? - package dependencies? - -Reviews -------- - -- score (1..10) -- title -- summary -- text -- author -- usefulness score - Grok nudges ----------- -- grok.Fields() does not pass on for_display, AutoFields() doesn't take it either +- grok.Fields() does not pass on for_display, AutoFields() doesn't + take it either - grok.AddForm doesn't have applyChanges -- can't override form template +- can't override form template? +- grok.Container is not attribute annotatable by default +- Form base class not available through grok, not grokkable From philikon at codespeak.net Thu Mar 15 23:21:22 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 23:21:22 +0100 (CET) Subject: [z3-checkins] r40560 - z3/NudgeNudge/trunk Message-ID: <20070315222122.03960100AA@code0.codespeak.net> Author: philikon Date: Thu Mar 15 23:21:21 2007 New Revision: 40560 Modified: z3/NudgeNudge/trunk/README.txt Log: Fixed a bunch of grok nudges :) Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Thu Mar 15 23:21:21 2007 @@ -31,9 +31,4 @@ Grok nudges ----------- -- grok.Fields() does not pass on for_display, AutoFields() doesn't - take it either -- grok.AddForm doesn't have applyChanges -- can't override form template? -- grok.Container is not attribute annotatable by default -- Form base class not available through grok, not grokkable +- form fields and for_display doesn't seem to work From philikon at codespeak.net Thu Mar 15 23:22:18 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 23:22:18 +0100 (CET) Subject: [z3-checkins] r40561 - z3/NudgeNudge/trunk Message-ID: <20070315222218.DD0A3100AA@code0.codespeak.net> Author: philikon Date: Thu Mar 15 23:22:16 2007 New Revision: 40561 Modified: z3/NudgeNudge/trunk/README.txt Log: actually, all grok nudges are gone Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Thu Mar 15 23:22:16 2007 @@ -27,8 +27,3 @@ - similar package association? - package dependencies? - -Grok nudges ------------ - -- form fields and for_display doesn't seem to work From philikon at codespeak.net Thu Mar 15 23:23:00 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Thu, 15 Mar 2007 23:23:00 +0100 (CET) Subject: [z3-checkins] r40562 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070315222300.3EEA9100AA@code0.codespeak.net> Author: philikon Date: Thu Mar 15 23:22:55 2007 New Revision: 40562 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Adjust to grok form refactoring and fix a missing import. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Thu Mar 15 23:22:55 2007 @@ -8,7 +8,7 @@ from zope.app.security.interfaces import IAuthentication from zc.catalog.catalogindex import ValueIndex from zope.app.authentication.groupfolder import GroupFolder, GroupInformation -from zope.app.authentication.principalfolder import PrincipalFolder +from zope.app.authentication.principalfolder import PrincipalFolder, InternalPrincipal from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager from zope.annotation.interfaces import IAttributeAnnotatable @@ -49,10 +49,11 @@ self.results = catalog.searchResults(package_name=query) -class CreateReview(grok.AddForm): +class CreateReview(grok.Form): grok.require('nudge.AddReview') - form_fields = grok.AutoFields(IReview) # TODO make package_name read-only + form_fields = grok.AutoFields(IReview) + form_fields['package_name'].for_display = True # XXX doesn't work??? @grok.action('Create') def create(self, **kw): @@ -67,7 +68,7 @@ grok.notify(grok.ObjectCreatedEvent(r)) self.redirect(self.url(r)) -class SignUp(grok.EditForm): # XXX Not really an edit form +class SignUp(grok.Form): form_fields = grok.Fields( name = schema.TextLine(title=u"Your name"), @@ -81,5 +82,5 @@ # TODO: validate password is equal to password_repeat pau = component.getUtility(IAuthentication) pau['principals'][email] = user = InternalPrincipal(email, password, name) - pau['groups']['reviewers'].principals += [user] + pau['groups']['reviewers'].principals += (user,) self.redirect('index') From ianb at codespeak.net Fri Mar 16 20:06:53 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Fri, 16 Mar 2007 20:06:53 +0100 (CET) Subject: [z3-checkins] r40609 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070316190653.C084B10091@code0.codespeak.net> Author: ianb Date: Fri Mar 16 20:06:52 2007 New Revision: 40609 Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini Log: Update the openplans section Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/development.ini (original) +++ z3/deliverance/DeliveranceVHoster/trunk/development.ini Fri Mar 16 20:06:52 2007 @@ -33,9 +33,5 @@ [app:openplans] use = main -remote_template = http://norewrite.openplans.org/VirtualHostBase/{scheme}/{domain}:{port}/openplans/projects/{project}/VirtualHostRoot/ -rule_template = - - - - +clean_environ_headers_regex = ^HTTP_X_OPENPLANS +init_domain = %(here)s/docs/example_init_domain.py From ianb at codespeak.net Fri Mar 16 20:08:14 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Fri, 16 Mar 2007 20:08:14 +0100 (CET) Subject: [z3-checkins] r40610 - z3/deliverance/DeliveranceVHoster/trunk Message-ID: <20070316190814.7B26910089@code0.codespeak.net> Author: ianb Date: Fri Mar 16 20:08:12 2007 New Revision: 40610 Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini Log: Update the openplans section again Modified: z3/deliverance/DeliveranceVHoster/trunk/development.ini ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/development.ini (original) +++ z3/deliverance/DeliveranceVHoster/trunk/development.ini Fri Mar 16 20:08:12 2007 @@ -35,3 +35,5 @@ use = main clean_environ_headers_regex = ^HTTP_X_OPENPLANS init_domain = %(here)s/docs/example_init_domain.py +zope_location = http://localhost:8080 +#default_theme_uri = ? From philikon at codespeak.net Fri Mar 16 20:47:50 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Fri, 16 Mar 2007 20:47:50 +0100 (CET) Subject: [z3-checkins] r40616 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070316194750.580B7100A6@code0.codespeak.net> Author: philikon Date: Fri Mar 16 20:47:48 2007 New Revision: 40616 Removed: z3/NudgeNudge/trunk/src/nudgenudge/README.txt Log: Remove pointless README.txt Deleted: /z3/NudgeNudge/trunk/src/nudgenudge/README.txt ============================================================================== --- /z3/NudgeNudge/trunk/src/nudgenudge/README.txt Fri Mar 16 20:47:48 2007 +++ (empty file) @@ -1,8 +0,0 @@ -Put your application code in this Python package. - -app_templates - Place Page Templates (file extension '.pt') in this directory. They - will automatically be associated as views with the model in app.py. - -static - Place static resources such as CSS, JS, images, etc. in here. From philikon at codespeak.net Sat Mar 17 19:12:51 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:12:51 +0100 (CET) Subject: [z3-checkins] r40638 - z3/deliverance/trunk Message-ID: <20070317181251.594321006E@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:12:49 2007 New Revision: 40638 Modified: z3/deliverance/trunk/setup.py Log: - Point entry point to correct module - Bugfix releases to lxml 1.2.x should be fine, too Modified: z3/deliverance/trunk/setup.py ============================================================================== --- z3/deliverance/trunk/setup.py (original) +++ z3/deliverance/trunk/setup.py Sat Mar 17 19:12:49 2007 @@ -18,7 +18,7 @@ packages=find_packages(exclude=[]), zip_safe=False, install_requires=[ - 'lxml==1.2', + 'lxml>=1.2', 'Paste', 'FormEncode', 'elementtree', @@ -29,7 +29,7 @@ include_package_data=True, entry_points=""" [paste.filter_app_factory] - main = deliverance.wsgifilter:make_filter + main = deliverance.wsgimiddleware:make_filter [console_scripts] deliverance-proxy = deliverance.proxycommand:main From philikon at codespeak.net Sat Mar 17 19:13:59 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:13:59 +0100 (CET) Subject: [z3-checkins] r40639 - z3/NudgeNudge/trunk Message-ID: <20070317181359.1EB461006E@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:13:57 2007 New Revision: 40639 Modified: z3/NudgeNudge/trunk/ (props changed) z3/NudgeNudge/trunk/buildout.cfg Log: Install zope.paste, PasteDeploy and Deliverance (the latter via svn external since there's no CheeseShop entry for it yet). Modified: z3/NudgeNudge/trunk/buildout.cfg ============================================================================== --- z3/NudgeNudge/trunk/buildout.cfg (original) +++ z3/NudgeNudge/trunk/buildout.cfg Sat Mar 17 19:13:57 2007 @@ -15,13 +15,16 @@ [buildout] parts = data instance test -develop = . +develop = . deliverance [instance] database = data eggs = setuptools grok zc.catalog + zope.paste + PasteDeploy + Deliverance nudgenudge recipe = zc.recipe.zope3instance user = grok:grok @@ -45,6 +48,7 @@ zope.app.keyreference zope.app.session zope.app.twisted + zope.paste grok grok-meta nudgenudge From philikon at codespeak.net Sat Mar 17 19:15:31 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:15:31 +0100 (CET) Subject: [z3-checkins] r40640 - in z3/NudgeNudge/trunk: etc src/nudgenudge/static Message-ID: <20070317181531.EF60610077@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:15:29 2007 New Revision: 40640 Added: z3/NudgeNudge/trunk/etc/ z3/NudgeNudge/trunk/etc/paste.ini z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml (contents, props changed) Log: Style NudgeNudge application using Deliverance. Enable by changing server type in zope.conf from 'HTTP' to 'Paste.Main'. Added: z3/NudgeNudge/trunk/etc/paste.ini ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/etc/paste.ini Sat Mar 17 19:15:29 2007 @@ -0,0 +1,8 @@ +[filter-app:Paste.Main] +use = egg:Deliverance +theme_uri = http://slashdot.org +rule_uri = /@@/nudgenudge/rules.xml +next = zope + +[app:Paste.Main] +paste.app_factory = zope.paste.application:zope_publisher_app_factory \ No newline at end of file Added: z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Sat Mar 17 19:15:29 2007 @@ -0,0 +1,7 @@ + + + + + + Added: z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml Sat Mar 17 19:15:29 2007 @@ -0,0 +1,10 @@ + + + + + + + + From philikon at codespeak.net Sat Mar 17 19:26:01 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:26:01 +0100 (CET) Subject: [z3-checkins] r40641 - z3/NudgeNudge/trunk/etc Message-ID: <20070317182601.8AA9A10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:25:59 2007 New Revision: 40641 Modified: z3/NudgeNudge/trunk/etc/paste.ini Log: Rename application section in paste.ini, was still called Paste.Main. Modified: z3/NudgeNudge/trunk/etc/paste.ini ============================================================================== --- z3/NudgeNudge/trunk/etc/paste.ini (original) +++ z3/NudgeNudge/trunk/etc/paste.ini Sat Mar 17 19:25:59 2007 @@ -4,5 +4,5 @@ rule_uri = /@@/nudgenudge/rules.xml next = zope -[app:Paste.Main] -paste.app_factory = zope.paste.application:zope_publisher_app_factory \ No newline at end of file +[app:zope] +paste.app_factory = zope.paste.application:zope_publisher_app_factory From philikon at codespeak.net Sat Mar 17 19:30:21 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:30:21 +0100 (CET) Subject: [z3-checkins] r40642 - in z3/NudgeNudge/trunk: etc src/nudgenudge/static Message-ID: <20070317183021.CC75C10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:30:19 2007 New Revision: 40642 Modified: z3/NudgeNudge/trunk/etc/paste.ini z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Log: Use the CheeseShop's HTML to skin NudgeNudge :) Modified: z3/NudgeNudge/trunk/etc/paste.ini ============================================================================== --- z3/NudgeNudge/trunk/etc/paste.ini (original) +++ z3/NudgeNudge/trunk/etc/paste.ini Sat Mar 17 19:30:19 2007 @@ -1,6 +1,6 @@ [filter-app:Paste.Main] use = egg:Deliverance -theme_uri = http://slashdot.org +theme_uri = http://cheeseshop.python.org/pypi rule_uri = /@@/nudgenudge/rules.xml next = zope Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Sat Mar 17 19:30:19 2007 @@ -3,5 +3,5 @@ xmlns="http://www.plone.org/deliverance" > - + From philikon at codespeak.net Sat Mar 17 19:56:06 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 19:56:06 +0100 (CET) Subject: [z3-checkins] r40643 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070317185606.9E2AF10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 19:56:03 2007 New Revision: 40643 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Cosmetic fix to add form for now Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Sat Mar 17 19:56:03 2007 @@ -49,11 +49,11 @@ self.results = catalog.searchResults(package_name=query) -class CreateReview(grok.Form): +class CreateReview(grok.AddForm): grok.require('nudge.AddReview') form_fields = grok.AutoFields(IReview) - form_fields['package_name'].for_display = True # XXX doesn't work??? + #form_fields['package_name'].for_display = True # XXX doesn't work??? @grok.action('Create') def create(self, **kw): From philikon at codespeak.net Sat Mar 17 20:19:58 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 20:19:58 +0100 (CET) Subject: [z3-checkins] r40644 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070317191958.3D0DE10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 20:19:55 2007 New Revision: 40644 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Wrap long lines Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Sat Mar 17 20:19:55 2007 @@ -31,13 +31,15 @@ class NudgeNudge(grok.Application, grok.Container): grok.local_utility(IntIds, IIntIds) # needed for the catalog grok.local_utility(Catalog, ICatalog, setup=setup_catalog) - grok.local_utility(PluggableAuthentication, IAuthentication, setup=setup_pau) + grok.local_utility(PluggableAuthentication, IAuthentication, + setup=setup_pau) grok.implements(IAttributeAnnotatable) @grok.subscribe(NudgeNudge, grok.IObjectAddedEvent) def set_permissions(app, event): role_manager = IPrincipalPermissionManager(app) - role_manager.grantPermissionToPrincipal('nudge.AddReview', 'nudge.groups.reviewers') + role_manager.grantPermissionToPrincipal('nudge.AddReview', + 'nudge.groups.reviewers') class Index(grok.View): @@ -81,6 +83,7 @@ def sign_up(self, name, email, password, password_repeat): # TODO: validate password is equal to password_repeat pau = component.getUtility(IAuthentication) - pau['principals'][email] = user = InternalPrincipal(email, password, name) + pau['principals'][email] = user = InternalPrincipal(email, password, + name) pau['groups']['reviewers'].principals += (user,) self.redirect('index') From philikon at codespeak.net Sat Mar 17 21:31:59 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 21:31:59 +0100 (CET) Subject: [z3-checkins] r40645 - z3/NudgeNudge/trunk/src/nudgenudge/static Message-ID: <20070317203159.20DEC10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 21:31:56 2007 New Revision: 40645 Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml Log: Also copy over the tag if it's there Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/standardrules.xml Sat Mar 17 21:31:56 2007 @@ -7,4 +7,6 @@ + From philikon at codespeak.net Sat Mar 17 21:32:49 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 21:32:49 +0100 (CET) Subject: [z3-checkins] r40646 - in z3/NudgeNudge/trunk/src/nudgenudge: app_templates review_templates Message-ID: <20070317203249.02C3210078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 21:32:48 2007 New Revision: 40646 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: No more custom CSS Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Sat Mar 17 21:32:48 2007 @@ -1,7 +1,6 @@ - + Nudge nudge... Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Sat Mar 17 21:32:48 2007 @@ -1,5 +1,4 @@ - NudgeNudge Review of @@ -7,8 +6,6 @@ [Package title] </span> -

From philikon at codespeak.net Sat Mar 17 21:45:20 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 21:45:20 +0100 (CET) Subject: [z3-checkins] r40647 - z3/NudgeNudge/trunk/src/nudgenudge/static Message-ID: <20070317204520.92D0810078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 21:45:17 2007 New Revision: 40647 Removed: z3/NudgeNudge/trunk/src/nudgenudge/static/style.css Log: No more custom styles Deleted: /z3/NudgeNudge/trunk/src/nudgenudge/static/style.css ============================================================================== --- /z3/NudgeNudge/trunk/src/nudgenudge/static/style.css Sat Mar 17 21:45:17 2007 +++ (empty file) @@ -1,20 +0,0 @@ -body { - font-family: serif; -} - -h1, h2, h3, h4 { - font-family: sans-serif; - background-color: #448844; - color: White; - padding: 0.3em; -} - -fieldset { - background-color: #E8FFE8; - margin-bottom: 1em; -} - -legend { - font-weight: bold; - color: #448844; -} From philikon at codespeak.net Sat Mar 17 21:53:25 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 17 Mar 2007 21:53:25 +0100 (CET) Subject: [z3-checkins] r40649 - z3/NudgeNudge/trunk/src/nudgenudge/app_templates Message-ID: <20070317205325.D5AFB10078@code0.codespeak.net> Author: philikon Date: Sat Mar 17 21:53:22 2007 New Revision: 40649 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Log: Add Monty Python quote Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Sat Mar 17 21:53:22 2007 @@ -29,5 +29,11 @@ +

And now for something completely different:

+ +
+ Nudge, nudge, know what I mean, know what I mean? +
+ From philikon at codespeak.net Mon Mar 19 05:22:44 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 19 Mar 2007 05:22:44 +0100 (CET) Subject: [z3-checkins] r40741 - z3/NudgeNudge/trunk/src/nudgenudge/review_templates Message-ID: <20070319042244.3C34C10094@code0.codespeak.net> Author: philikon Date: Mon Mar 19 05:22:42 2007 New Revision: 40741 Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Clean up template Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Mon Mar 19 05:22:42 2007 @@ -8,40 +8,20 @@ -

- x - out of - y - people found this review useful. -

- -
- Was this review useful to you? - - -
- -

Review of - - - version - + frobnaz + v1.1

-

Author

- - -

Score

- +

by Dead Parrot

+ +

Score: 10 out of 10

Summary

-
- [Summary of the review] +
+ Summary goes here

Full Review

@@ -50,6 +30,19 @@ [Full review]
+

+ x + out of + y + people found this review useful. +

+ +
+ Was this review useful to you? + + +
+ From philikon at codespeak.net Mon Mar 19 05:24:14 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 19 Mar 2007 05:24:14 +0100 (CET) Subject: [z3-checkins] r40742 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070319042414.148F010094@code0.codespeak.net> Author: philikon Date: Mon Mar 19 05:24:12 2007 New Revision: 40742 Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py Log: - Move default values on Review model from constructor arguments to class-level attributes - Move useful score to annotation adapter - Add (protected) edit form Modified: z3/NudgeNudge/trunk/src/nudgenudge/review.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review.py Mon Mar 19 05:24:12 2007 @@ -1,5 +1,6 @@ import grok from BTrees.OOBTree import OOBTree +from zope import interface from zope.component import getUtility from zope.app.session.interfaces import IClientIdManager from nudgenudge.interfaces import IReview @@ -7,26 +8,42 @@ class Review(grok.Model): grok.implements(IReview) - def __init__(self, **kw): - # XXX prefer using applyChanges in the add form instead of this - for key, value in kw.items(): - setattr(self, key, value) + package_name = '' + version = '' + summary = u'' + text = u'' + score = None + author = u'' + +class IUsefulScores(interface.Interface): + useful_scores = interface.Attribute('useful_scores') + +class UsefulScores(grok.Annotation): + grok.implements(IUsefulScores) + + def __init__(self): self.useful_scores = OOBTree() class Index(grok.View): def update(self): + scores = IUsefulScores(self.context) self.useful_number = len([score for score in - self.context.useful_scores.values() if score]) - self.useful_total = len(self.context.useful_scores) + scores.useful_scores.values() if score]) + self.useful_total = len(scores.useful_scores) client_id = getUtility(IClientIdManager).getClientId(self.request) - self.show_score_form = client_id not in self.context.useful_scores + self.show_score_form = client_id not in scores.useful_scores class Score(grok.View): def render(self, score): score = score.lower() == 'yes' client_id = getUtility(IClientIdManager).getClientId(self.request) - if client_id not in self.context.useful_scores: - self.context.useful_scores[client_id] = score + scores = IUsefulScores(self.context) + if client_id not in scores.useful_scores: + scores.useful_scores[client_id] = score self.redirect('index') + +class Edit(grok.EditForm): + grok.require('nudge.EditReview') + form_fields = grok.AutoFields(IReview) From philikon at codespeak.net Mon Mar 19 05:26:49 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 19 Mar 2007 05:26:49 +0100 (CET) Subject: [z3-checkins] r40743 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070319042649.A6B3B10094@code0.codespeak.net> Author: philikon Date: Mon Mar 19 05:26:47 2007 New Revision: 40743 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: - Use roles instead of groups to assign permissions to newly signed up users. Doesn't seem to work, though, maybe because it's a local role and a local grant??? - Fix the way we register the authenticator plugin - Work with new Review() constructor, use applyChanges to save the data - Index the 'author' attribute of reviews Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Mon Mar 19 05:26:47 2007 @@ -5,12 +5,14 @@ from zope.app.catalog.catalog import Catalog from zope.app.catalog.interfaces import ICatalog from zope.app.authentication import PluggableAuthentication +from zope.app.authentication.principalfolder import (PrincipalFolder, + InternalPrincipal) from zope.app.security.interfaces import IAuthentication +from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, IRole +from zope.app.securitypolicy.interfaces import IRolePermissionManager +from zope.app.securitypolicy.role import LocalRole + from zc.catalog.catalogindex import ValueIndex -from zope.app.authentication.groupfolder import GroupFolder, GroupInformation -from zope.app.authentication.principalfolder import PrincipalFolder, InternalPrincipal -from zope.app.securitypolicy.interfaces import IPrincipalPermissionManager -from zope.annotation.interfaces import IAttributeAnnotatable from nudgenudge.interfaces import IReview from nudgenudge.review import Review @@ -21,25 +23,30 @@ def setup_catalog(catalog): catalog['package_name'] = ValueIndex('package_name', IReview, False) + catalog['author'] = ValueIndex('author', IReview, False) def setup_pau(pau): - pau['groups'] = groups = GroupFolder('nudge.groups.') - groups['reviewers'] = GroupInformation('Nudge Reviewers') pau['principals'] = principals = PrincipalFolder('nudge.principals.') - pau.credentialPlugins = ('groups', 'principals',) + pau.authenticatorPlugins = ('principals',) + +def role_factory(*args): + def factory(): + return LocalRole(*args) + return factory class NudgeNudge(grok.Application, grok.Container): grok.local_utility(IntIds, IIntIds) # needed for the catalog grok.local_utility(Catalog, ICatalog, setup=setup_catalog) grok.local_utility(PluggableAuthentication, IAuthentication, setup=setup_pau) - grok.implements(IAttributeAnnotatable) + grok.local_utility(role_factory(u'Nudge Reviewers'), IRole, + name='nudge.Reviewers', + name_in_container='nudge.Reviewers') @grok.subscribe(NudgeNudge, grok.IObjectAddedEvent) -def set_permissions(app, event): - role_manager = IPrincipalPermissionManager(app) - role_manager.grantPermissionToPrincipal('nudge.AddReview', - 'nudge.groups.reviewers') +def grant_permissions(app, event): + role_manager = IRolePermissionManager(app) + role_manager.grantPermissionToRole('nudge.AddReview', 'nudge.Reviewers') class Index(grok.View): @@ -50,7 +57,6 @@ query = {'any_of': (package,)} self.results = catalog.searchResults(package_name=query) - class CreateReview(grok.AddForm): grok.require('nudge.AddReview') @@ -59,7 +65,9 @@ @grok.action('Create') def create(self, **kw): - r = Review(**kw) + r = Review() + self.applyChanges(r, **kw) + base = name = kw['package_name'] + '-' + kw['version'] count = 0 while name in self.context: @@ -74,7 +82,8 @@ form_fields = grok.Fields( name = schema.TextLine(title=u"Your name"), - email = schema.TextLine(title=u"Email address"), # TODO: validate email address + # TODO: validate email address + email = schema.TextLine(title=u"Email address"), password = schema.Password(title=u"Password"), password_repeat = schema.Password(title=u"Repeat password"), ) @@ -82,8 +91,15 @@ @grok.action('Sign up') def sign_up(self, name, email, password, password_repeat): # TODO: validate password is equal to password_repeat + + # add principal to principal folder pau = component.getUtility(IAuthentication) - pau['principals'][email] = user = InternalPrincipal(email, password, - name) - pau['groups']['reviewers'].principals += (user,) + principals = pau['principals'] + principals[email] = user = InternalPrincipal(email, password, name) + + # grant principal the role + role_manager = IPrincipalRoleManager(self.context) + role_manager.assignRoleToPrincipal('nudge.Reviewers', + principals.prefix + email) + self.redirect('index') From philikon at codespeak.net Mon Mar 19 05:28:18 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 19 Mar 2007 05:28:18 +0100 (CET) Subject: [z3-checkins] r40744 - z3/NudgeNudge/trunk/src/nudgenudge Message-ID: <20070319042818.56BEE10094@code0.codespeak.net> Author: philikon Date: Mon Mar 19 05:28:16 2007 New Revision: 40744 Modified: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py Log: - Fix indentation caused by TextMate's inability to comply to standard Python formatting rules (PEP8) - Rating from 1..5 is sufficient, 0..9 is too confusing since it gives too much choice Modified: z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/interfaces.py Mon Mar 19 05:28:16 2007 @@ -5,25 +5,25 @@ package_name = ASCIILine( title=u'Package name', - ) + ) version = ASCIILine( title=u'Version', - ) + ) summary = Text( title=u'Summary', - ) + ) text = Text( title=u'Text' - ) + ) score = Choice( title=u'Score', - values=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ) + values=[1, 2, 3, 4, 5] + ) author = TextLine( title=u'Author', - ) + ) From ianb at codespeak.net Mon Mar 19 18:09:42 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Mon, 19 Mar 2007 18:09:42 +0100 (CET) Subject: [z3-checkins] r40784 - z3/deliverance/trunk/deliverance Message-ID: <20070319170942.D7BA810068@code0.codespeak.net> Author: ianb Date: Mon Mar 19 18:09:40 2007 New Revision: 40784 Modified: z3/deliverance/trunk/deliverance/proxyapp.py Log: Added a better / more complete entry point for setting up the proxy Modified: z3/deliverance/trunk/deliverance/proxyapp.py ============================================================================== --- z3/deliverance/trunk/deliverance/proxyapp.py (original) +++ z3/deliverance/trunk/deliverance/proxyapp.py Mon Mar 19 18:09:40 2007 @@ -3,7 +3,12 @@ passing the request to another HTTP server """ +import os +import urlparse from paste.proxy import TransparentProxy +from paste.urlmap import URLMap +from paste.urlparser import StaticURLParser +from paste.exceptions import errormiddleware from deliverance.wsgimiddleware import DeliveranceMiddleware from deliverance.relocateresponse import RelocateMiddleware @@ -53,6 +58,25 @@ environ, start_response) +class ProxyMountedDeliveranceApp(ProxyDeliveranceApp): + + def __init__(self, *args, **kw): + try: + mount_points = kw.pop('mount_points') + except KeyError: + mount_points = {} + self.mount_points = mount_points + ProxyDeliveranceApp.__init__(self, *args, **kw) + + def make_app(self): + normal_app = ProxyDeliveranceApp.make_app(self) + from paste.urlmap import URLMap + urlmap = URLMap() + for name, value in self.mount_points.items(): + urlmap[name] = value + urlmap['/'] = normal_app + return urlmap + class DebugHeaders(object): translate_keys = {'CONTENT_LENGTH': 'HTTP_CONTENT_LENGTH', @@ -77,3 +101,55 @@ print ' %s: %s' % (name.title(), value) start_response(status, headers, exc_info) return self.app(environ, repl_start_response) + + +def make_proxy(global_conf, + wrap_href, theme_uri, rule_uri, + renderer='py', transparent=False, debug_headers=False, + relocate_content=False, + merge_cache_control=False, + **kw): + from paste.deploy.converters import asbool + mount_points = {} + for name, value in kw.items(): + if name.startswith('mount '): + path = name[len('mount '):].strip() + if not path: + raise ValueError('Bad path: %r (in %r)' % (path, name)) + mount_points[path] = StaticURLParser(os.path.abspath(value)) + else: + raise ValueError( + "Unexpected configuration key: %r" % name) + if not wrap_href.startswith('http:') or wrap_href.startswith('https:'): + wrap_href = 'http://' + wrap_href.lstrip('/') + parts = urlparse.urlsplit(wrap_href) + scheme, netloc, path, query, fragment = parts + scheme = scheme.lower() + if scheme not in ['http', 'https']: + raise ValueError( + "I don't know how to proxy to the scheme %r (from wrap_href=%s)" + % (scheme, wrap_href)) + if fragment: + raise ValueError( + "You cannot use a fragment (%r) in wrap_href=%s" + % (fragment, wrap_href)) + if query: + raise ValueError( + "You cannot use a query string ?%s (from wrap_href=%s)" + % (query, wrap_href)) + if path and path != '/': + raise ValueError( + "Proxying to a path on a server is not currently supported " + "(path=%r from wrap_href=%s)" + % (path, wrap_href)) + app = ProxyMountedDeliveranceApp( + theme_uri=theme_uri, + rule_uri=rule_uri, + proxy=netloc, + transparent=asbool(transparent), + debug_headers=asbool(debug_headers), + relocate_content=asbool(relocate_content), + renderer=renderer, + mount_points=mount_points) + app = errormiddleware.make_error_middleware(app, global_conf) + return app From ianb at codespeak.net Mon Mar 19 18:12:08 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Mon, 19 Mar 2007 18:12:08 +0100 (CET) Subject: [z3-checkins] r40785 - in z3/deliverance/trunk: . deliverance deliverance/new_layout_template deliverance/new_layout_template/bin deliverance/new_layout_template/etc deliverance/new_layout_template/log deliverance/new_layout_template/static deliverance/new_layout_template/static/rules deliverance/new_layout_template/var Message-ID: <20070319171208.A014310068@code0.codespeak.net> Author: ianb Date: Mon Mar 19 18:12:05 2007 New Revision: 40785 Added: z3/deliverance/trunk/deliverance/new_layout_template/ z3/deliverance/trunk/deliverance/new_layout_template/bin/ z3/deliverance/trunk/deliverance/new_layout_template/etc/ z3/deliverance/trunk/deliverance/new_layout_template/etc/deliverance-proxy.ini z3/deliverance/trunk/deliverance/new_layout_template/log/ z3/deliverance/trunk/deliverance/new_layout_template/static/ z3/deliverance/trunk/deliverance/new_layout_template/static/rules/ z3/deliverance/trunk/deliverance/new_layout_template/static/rules/rules.xml z3/deliverance/trunk/deliverance/new_layout_template/static/rules/standardrules.xml (contents, props changed) z3/deliverance/trunk/deliverance/new_layout_template/static/theme.html z3/deliverance/trunk/deliverance/new_layout_template/var/ z3/deliverance/trunk/deliverance/proxyconfig.py z3/deliverance/trunk/deliverance/staticcommand.py Modified: z3/deliverance/trunk/ (props changed) z3/deliverance/trunk/deliverance/proxycommand.py z3/deliverance/trunk/setup.py Log: the start (not yet complete) of a static rendering command and a command to setup a new environment with all the necessary configuration to run the proxy Added: z3/deliverance/trunk/deliverance/new_layout_template/etc/deliverance-proxy.ini ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/new_layout_template/etc/deliverance-proxy.ini Mon Mar 19 18:12:05 2007 @@ -0,0 +1,36 @@ +[DEFAULT] +## Uncomment this to show exceptions in the browser +## (ideally there should be no exceptions) +#debug = true +## Set this to have any errors emailed to you, including problems +## with the server +#error_email = johndoe at example.com, janedoe at example.org + +[server:main] +use = egg:Paste#http +## This is the interface to bind to (0.0.0.0 means all interfaces): +host = 0.0.0.0 +## And the port to serve on: +port = 8000 + +[app:main] +use = egg:Deliverance#proxy +## This is the site that is being wrapped: +wrap_href = http://plone.org +## These refer to the files in static/, and you probably don't need to +## change these: +theme_uri = /.deliverance/static/theme.html +rule_uri = /.deliverance/static/rules/rules.xml +mount /.deliverance/static = %(here)s/../static + +[exe] +## These options control the daemon process +command = serve +## Note that %(here)s means the directory containing this config file +pid_file = %(here)s/../var/deliverance-proxy.pid +log_file = %(here)s/../log/deliverance-proxy.log +daemon = true +## The user and group options can be used if you start this as root +## (the server will then change to this user/group): +#user = username +#group = groupname Added: z3/deliverance/trunk/deliverance/new_layout_template/static/rules/rules.xml ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/new_layout_template/static/rules/rules.xml Mon Mar 19 18:12:05 2007 @@ -0,0 +1,6 @@ + + + + + + Added: z3/deliverance/trunk/deliverance/new_layout_template/static/rules/standardrules.xml ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/new_layout_template/static/rules/standardrules.xml Mon Mar 19 18:12:05 2007 @@ -0,0 +1,9 @@ + + + + + + + + Added: z3/deliverance/trunk/deliverance/new_layout_template/static/theme.html ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/new_layout_template/static/theme.html Mon Mar 19 18:12:05 2007 @@ -0,0 +1,18 @@ + + + + +The Theme + + + +

A theme

+ +
+
+ + +
+
+Last modified: Fri Mar 16 09:33:37 CDT 2007 + Modified: z3/deliverance/trunk/deliverance/proxycommand.py ============================================================================== --- z3/deliverance/trunk/deliverance/proxycommand.py (original) +++ z3/deliverance/trunk/deliverance/proxycommand.py Mon Mar 19 18:12:05 2007 @@ -1,3 +1,4 @@ +import os import optparse import pkg_resources import sys @@ -15,6 +16,10 @@ parser = optparse.OptionParser( version=str(my_package), usage="%%prog [OPTIONS]\n\n%s" % help) +parser.add_option('--new-layout', + dest="new_layout", + metavar="DEST_DIR", + help="Create a self-contained layout for running the proxy server, with a pre-built theme, rules, and configuration file") parser.add_option('-s', '--serve', help="The interface to serve on (default 0.0.0.0:80)", dest="serve", @@ -63,6 +68,9 @@ if args is None: args = sys.argv[1:] options, args = parser.parse_args(args) + if options.new_layout: + make_new_layout(options.new_layout) + return serve = strip('http://', options.serve) if ':' not in serve: serve += ':80' @@ -103,3 +111,10 @@ print 'Exiting.' sys.exit() +def make_new_layout(dest_dir): + source = os.path.join(os.path.dirname(__file__), 'new_layout_template') + from paste.script import copydir + copydir.copy_dir( + source, dest_dir, {}, 1, simulate=False, interactive=True, + svn_add=False) + Added: z3/deliverance/trunk/deliverance/proxyconfig.py ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/proxyconfig.py Mon Mar 19 18:12:05 2007 @@ -0,0 +1,34 @@ +""" +This module contains the commands used in deliverance file layouts, +where you have a deliverance-proxy-run and deliverance-proxy-ctl +commands bound a specific configuration. +""" + +import os +import sys +from paste.script import serve +from paste.script import exe + +def proxy_run(script_filename): + """ + Using script_filename, this determines the configuration and executes + ``paster serve`` + """ + config_filename = find_config(script_filename) + cmd = serve.ServeCommand('serve') + cmd.run([config_filename] + sys.argv[1:]) + +def proxy_ctl(script_filename): + """ + Using script_filename, this determines the configuration and executes + ``paster serve --daemon`` plus the given commands + """ + config_filename = find_config(script_filename) + os.environ['_'] = config_filename + cmd = exe.ExeCommand('exe') + cmd.run([config_filename] + sys.argv[1:]) + +def find_config(script_filename): + return os.path.join( + os.path.dirname(os.path.dirname(script_filename)), + 'etc', 'deliverance-proxy.ini') Added: z3/deliverance/trunk/deliverance/staticcommand.py ============================================================================== --- (empty file) +++ z3/deliverance/trunk/deliverance/staticcommand.py Mon Mar 19 18:12:05 2007 @@ -0,0 +1,136 @@ +""" +Command for rendering a set of static files to another set of static +files. Not yet complete. +""" + +import optparse +import sys +import os +import re +import urllib +import cgi +import pkg_resources + +my_package = pkg_resources.get_distribution('Deliverance') + +scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) +drive_re = re.compile(r'^[a-z]:', re.I) + +help = """\ +Render the FILES (or directories) given the THEME and RULES +(or a default set of rules generated by the rule-* options)""" + +parser = optparse.OptionParser( + version=str(my_package), + usage="%%prog [OPTIONS] INPUT_DIR OUTPUT_DIR\n\n%s" % help) +parser.add_option('-v', '--verbose', + help="Be more verbose", + dest="verbose", + action="count") +parser.add_option('-q', '--quiet', + help="Be more quiet", + dest="quiet", + action="count") +parser.add_option('-t', '--theme', + help="The URI of the theme to use", + metavar="URI/FILE", + dest="theme") +parser.add_option('-r', '--rule', + help="The URI of the ruleset to use", + metavar="URI/FILE", + dest="rule") +parser.add_option('--rule-theme-body', + metavar="XPATH", + help="If no rules provided, use this XPath expression to locate the body", + dest="theme_body_xpath") +parser.add_option('--rule-content-body', + metavar="XPATH", + help="If no rules provided, use this XPath expression to locate the content body") +parser.add_option('--renderer', + dest="renderer", + metavar="NAME", + help="Select which renderer to use: 'py' or 'xslt'", + default='py') + +class BadCommand(Exception): + pass + +def run_command(options, args): + if len(args) < 2: + raise BadCommand( + "You must give a INPUT_DIR and OUTPUT_DIR argument") + elif len(args) > 2: + raise BadCommand( + "You can only give two arguments, INPUT_DIR and OUTPUT_DIR") + input_dir, output_dir = args + if not options.rule: + options.rule = make_rule(options) + else: + options.rule = make_uri(options.rule) + options.verbose += 1 + options.verbose -= options.quiet + del options.quiet + if not options.theme: + raise BadCommand( + "You must give an argument for --theme") + options.theme = make_uri(options.theme) + loader = make_loader(input_dir, options.rule) + norm_input_dir = os.path.abspath(input_dir) + renderer = None + for fn in all_files(input_dir): + full_fn = os.path.abspath(fn) + assert full_fn.startswith(input_dir) + plain_fn = full_fn[len(input_dir):].lstrip(os.path.sep) + dest_fn = os.path.join(output_dir, plain_fn) + ext = os.path.splitext(fn)[1].lower() + if ext not in ('.html', '.xhtml'): + if options.verbose > 2: + print 'Copying %s' % fn + shutil.copy(fn, dest_fn) + continue + if options.verbose > 2: + print 'Rendering %s' + contents = transform_file(renderer, loader, fn) + f = open(dest_fn, 'wb') + f.write(contents) + f.close() + + +def all_files(dir): + for dirpath, dirnames, filenames in os.walk(dir): + for filename in filenames: + yield os.path.join(dirpath, filename) + +def make_uri(maybe_uri): + """ + Returns the argument as a URI. The argument may be a relative or + absolute file path, in which case it is turned into an absolute + file: URI. + """ + if scheme_re.search(maybe_uri): + return maybe_uri + maybe_uri = os.path.abspath(maybe_uri) + if os.path.sep != '/': + maybe_uri = maybe_uri.replace(os.path.sep, '/') + maybe_uri = urllib.quote(maybe_uri) + if sys.platform == 'win32': + match = drive_re.search(maybe_uri) + if match: + maybe_uri = '/' + maybe_uri[:match.end()-1] + '|' + maybe_uri[match.end():] + return 'file://' + maybe_uri + +def main(args=None): + if args is None: + args = sys.argv[1:] + options, args = parser.parse_args(args) + try: + run_command(options, args) + except BadCommand, e: + print e + parser.print_help() + sys.exit(2) + + +if __name__ == '__main__': + main() + Modified: z3/deliverance/trunk/setup.py ============================================================================== --- z3/deliverance/trunk/setup.py (original) +++ z3/deliverance/trunk/setup.py Mon Mar 19 18:12:05 2007 @@ -31,11 +31,15 @@ [paste.filter_app_factory] main = deliverance.wsgimiddleware:make_filter + [paste.app_factory] + proxy = deliverance.proxyapp:make_proxy + [console_scripts] deliverance-proxy = deliverance.proxycommand:main deliverance-tests = deliverance.testrunner:main deliverance-speed = deliverance.test_speed:main deliverance-handtransform = deliverance.handtransform:main + deliverance-static = deliverance.staticcommand:main """, ) From philikon at codespeak.net Mon Mar 19 19:49:09 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 19 Mar 2007 19:49:09 +0100 (CET) Subject: [z3-checkins] r40791 - z3/NudgeNudge/trunk Message-ID: <20070319184909.30D6B10068@code0.codespeak.net> Author: philikon Date: Mon Mar 19 19:49:08 2007 New Revision: 40791 Modified: z3/NudgeNudge/trunk/README.txt Log: minor restructuring Modified: z3/NudgeNudge/trunk/README.txt ============================================================================== --- z3/NudgeNudge/trunk/README.txt (original) +++ z3/NudgeNudge/trunk/README.txt Mon Mar 19 19:49:08 2007 @@ -1,20 +1,20 @@ NudgeNudge ========== -A Python package review site. +A Python package review application. Goals ----- -* Visitors can sign up become reviewers -* Reviewers can write reviews of Python packages -* Visitors can search for reviews +* Support the following user stories: + - Visitors can sign up become reviewers + - Reviewers can write reviews of Python packages + - Visitors can search for reviews + +* Don't store any information that's already in PyPI (basically, store + no information about packages, just reviews), though possibly + integrate with PyPI using its XMLRPC API where necessary. -Non-Goals ---------- - -* Store any information that's already in PyPI (basically, store no - information about packages, just reviews) Possible Features ----------------- From ianb at codespeak.net Mon Mar 19 21:02:09 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Mon, 19 Mar 2007 21:02:09 +0100 (CET) Subject: [z3-checkins] r40792 - in z3/deliverance/DeliveranceVHoster/trunk: dvhoster tests Message-ID: <20070319200209.E565E10068@code0.codespeak.net> Author: ianb Date: Mon Mar 19 21:02:07 2007 New Revision: 40792 Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Log: Fix issue with trailing slash redirects (trailing /'s were eaten) Modified: z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/dvhoster/dispatcher.py Mon Mar 19 21:02:07 2007 @@ -19,7 +19,10 @@ if not urlpath: urlpath = '/' assert urlpath.startswith('/') + last_slash = urlpath.endswith('/') urlpath = posixpath.normpath(posixpath.abspath(urlpath)) + if last_slash: + urlpath += '/' return urlpath class DeliveranceDispatcher(object): @@ -56,6 +59,7 @@ environ, with_query_string=False, path_info='') path_info = norm_path(environ.get('PATH_INFO', '')) + print 'path_info', repr(path_info), repr(environ['PATH_INFO']) if path_info.startswith('/.deliverance'): path_info_pop(environ) subapp = ProviderApp(domain_info) @@ -131,6 +135,7 @@ if not path.endswith('/'): path += '/' if path_info + '/' == path: + print 'redirect', [remote_uri_info, path, path_info] exc = httpexceptions.HTTPMovedPermanently( headers=[('location', construct_url(environ, path_info=path_info+'/'))]) return exc(environ, start_response) Modified: z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py ============================================================================== --- z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py (original) +++ z3/deliverance/DeliveranceVHoster/trunk/tests/test_functional_api.py Mon Mar 19 21:02:07 2007 @@ -81,6 +81,14 @@ # It gets normalized, so it doesn't actually stay the same: #assert app.get('/.deliverance/remote_uris').body == data + res = app.get('/bar', status=301) + assert res.header('location') == 'http://localhost/bar/' + ## @@: Right now there's a problem with StaticURLParser where it + ## does a bad redirect here... + #res = res.follow() + #print res + #assert res.status == 200 + data = ''' [{"path": "/test1.html", "rewrite": "/test1"}, {"prefix": "/test2", "rewrite": "/test3", "comment": "rename"}, From philikon at codespeak.net Tue Mar 20 01:25:42 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 01:25:42 +0100 (CET) Subject: [z3-checkins] r40802 - z3/NudgeNudge/trunk/src/nudgenudge/app_templates Message-ID: <20070320002542.EA3B810063@code0.codespeak.net> Author: philikon Date: Tue Mar 20 01:25:38 2007 New Revision: 40802 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Log: Better Monty Python quote Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Tue Mar 20 01:25:38 2007 @@ -32,7 +32,7 @@

And now for something completely different:

- Nudge, nudge, know what I mean, know what I mean? + Say no more, say no more, know what I mean, nudge nudge?
From philikon at codespeak.net Tue Mar 20 01:29:27 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 01:29:27 +0100 (CET) Subject: [z3-checkins] r40803 - in z3/NudgeNudge/trunk/src/nudgenudge: app_templates review_templates Message-ID: <20070320002927.81AC110063@code0.codespeak.net> Author: philikon Date: Tue Mar 20 01:29:24 2007 New Revision: 40803 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Untabify and re-indent templates. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Tue Mar 20 01:29:24 2007 @@ -1,39 +1,39 @@ - - Nudge nudge... - - - -
-

Find a review of...

- -
- - -
-
- - - -

Write a review of...

- -
- - -
- -

And now for something completely different:

- -
- Say no more, say no more, know what I mean, nudge nudge? -
+ + Nudge nudge... + + - +
+

Find a review of...

+ +
+ + +
+
+ + + +

Write a review of...

+ +
+ + +
+ +

And now for something completely different:

+ +
+ Say no more, say no more, know what I mean, nudge nudge? +
+ + Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Tue Mar 20 01:29:24 2007 @@ -1,48 +1,47 @@ - - - NudgeNudge Review of - <span tal:replace="context/package_name"> - [Package title] - </span> - - - - -

- Review of - frobnaz - v1.1 -

- -

by Dead Parrot

- -

Score: 10 out of 10

- -

Summary

-
- Summary goes here -
- -

Full Review

-
- [Full review] -
- -

- x - out of - y - people found this review useful. -

- -
- Was this review useful to you? - - -
- - + + + NudgeNudge Review of + <span tal:replace="context/package_name"> + [Package title] + </span> + + + + +

+ Review of + frobnaz + v1.1 +

+ +

by Dead Parrot

+ +

Score: 10 out of 10

+ +

Summary

+
+ Summary goes here +
+ +

Full Review

+
+ [Full review] +
+ +

+ x + out of + y + people found this review useful. +

+ +
+ Was this review useful to you? + + +
+ From philikon at codespeak.net Tue Mar 20 02:01:49 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 02:01:49 +0100 (CET) Subject: [z3-checkins] r40804 - z3/NudgeNudge/trunk Message-ID: <20070320010149.E0B0410068@code0.codespeak.net> Author: philikon Date: Tue Mar 20 02:01:49 2007 New Revision: 40804 Added: z3/NudgeNudge/trunk/INSTALL.txt (contents, props changed) Log: Add install docs Added: z3/NudgeNudge/trunk/INSTALL.txt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/INSTALL.txt Tue Mar 20 02:01:49 2007 @@ -0,0 +1,43 @@ +Installing NudgeNudge +===================== + +Note that this currently only works on Unix (Linux, Mac, ...) systems: + +1. Check out the project area from SVN:: + + $ svn co http://codespeak.net/svn/z3/NudgeNudge/trunk NudgeNudge + +2. Edit buildout.cfg to point to your Zope 3 installation:: + + [zope3] + location = /path/to/Zope-3.3.x + +3. Bootstrap zc.buildout to obtain the ``buildout`` script:: + + $ python2.4 bootstrap/bootstrap.py + +4. Run the buildout to install required packages (grok, deliverance, + paste.deploy, zope.paste, etc.) and create the Zope 3 instance + automatically:: + + $ bin/buildout + ...lots of output here... + +5. Start Zope:: + + $ parts/instance/bin/runzope + + You will now be able to create a NudgeNudge application object by + pointing your webbrowser to http://localhost:8080. + +6. To switch on the theming middleware, edit ``parts/etc/zope.conf`` + and change the server type from HTTP to Paste.Main:: + + + type Paste.Main + address 8080 + + + Note that you'll have to restart Zope after this change. Also note + that the Deliverance middleware requires lxml to do the theming + which is known to have problems on certain platforms, e.g. Mac OS X. From philikon at codespeak.net Tue Mar 20 04:47:17 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 04:47:17 +0100 (CET) Subject: [z3-checkins] r40806 - in z3/NudgeNudge/trunk/src/nudgenudge: . app_templates macro_templates review_templates Message-ID: <20070320034717.E194B1005A@code0.codespeak.net> Author: philikon Date: Tue Mar 20 04:47:16 2007 New Revision: 40806 Added: z3/NudgeNudge/trunk/src/nudgenudge/macro.py (contents, props changed) z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt (contents, props changed) Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Log: Add master macro and use it in the two views we have so far. Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Tue Mar 20 04:47:16 2007 @@ -1,8 +1,6 @@ - - - Nudge nudge... - + +

Find a review of...

@@ -35,5 +33,6 @@ Say no more, say no more, know what I mean, nudge nudge? +
Added: z3/NudgeNudge/trunk/src/nudgenudge/macro.py ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro.py Tue Mar 20 04:47:16 2007 @@ -0,0 +1,6 @@ +import grok +from zope.interface import Interface + +class Master(grok.View): + # register this view for all objects + grok.context(Interface) Added: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Tue Mar 20 04:47:16 2007 @@ -0,0 +1,13 @@ + + + Nudge Nudge + + + +

Nudge Nudge

+ +
+ +
+ + Modified: z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/review_templates/index.pt Tue Mar 20 04:47:16 2007 @@ -1,30 +1,30 @@ - + - + <title metal:fill-slot="title"> NudgeNudge Review of <span tal:replace="context/package_name"> [Package title] </span> - -

+
+

Review of frobnaz v1.1 -

+

by Dead Parrot

Score: 10 out of 10

-

Summary

+

Summary

Summary goes here
-

Full Review

+

Full Review

[Full review] @@ -43,5 +43,6 @@ +
From philikon at codespeak.net Tue Mar 20 05:03:09 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 05:03:09 +0100 (CET) Subject: [z3-checkins] r40807 - z3/NudgeNudge/trunk/src/nudgenudge/app_templates Message-ID: <20070320040309.E4EDA1005A@code0.codespeak.net> Author: philikon Date: Tue Mar 20 05:03:09 2007 New Revision: 40807 Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Log: Improve form flow of front page Modified: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/index.pt Tue Mar 20 05:03:09 2007 @@ -2,25 +2,34 @@
-
-

Find a review of...

+
+

Find a review of...

- +
-
- +
+

Your search returned no results.

+ + +

Your search returned the following results:

+ +
+
+ +
-

Write a review of...

+

Write a review of...

From philikon at codespeak.net Tue Mar 20 17:54:23 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 17:54:23 +0100 (CET) Subject: [z3-checkins] r40849 - in z3/NudgeNudge/trunk/src/nudgenudge: macro_templates static Message-ID: <20070320165423.8A0201006E@code0.codespeak.net> Author: philikon Date: Tue Mar 20 17:54:22 2007 New Revision: 40849 Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Log: Insert breadcrumbs Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Tue Mar 20 17:54:22 2007 @@ -4,10 +4,25 @@ -

Nudge Nudge

+ -
+

+Nudge Nudge +

+
+
+ Body goes here +
+ Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Tue Mar 20 17:54:22 2007 @@ -3,5 +3,6 @@ xmlns="http://www.plone.org/deliverance" > - + + From philikon at codespeak.net Tue Mar 20 18:09:58 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 18:09:58 +0100 (CET) Subject: [z3-checkins] r40850 - z3/NudgeNudge/trunk Message-ID: <20070320170958.0B7631005A@code0.codespeak.net> Author: philikon Date: Tue Mar 20 18:09:56 2007 New Revision: 40850 Modified: z3/NudgeNudge/trunk/INSTALL.txt Log: Let users know that they can see both the themed and unthemed app by simply *adding* a server definition instead of replacing the existing one. Modified: z3/NudgeNudge/trunk/INSTALL.txt ============================================================================== --- z3/NudgeNudge/trunk/INSTALL.txt (original) +++ z3/NudgeNudge/trunk/INSTALL.txt Tue Mar 20 18:09:56 2007 @@ -31,13 +31,17 @@ pointing your webbrowser to http://localhost:8080. 6. To switch on the theming middleware, edit ``parts/etc/zope.conf`` - and change the server type from HTTP to Paste.Main:: + and add the following server definition:: type Paste.Main - address 8080 + address 8081 - Note that you'll have to restart Zope after this change. Also note - that the Deliverance middleware requires lxml to do the theming - which is known to have problems on certain platforms, e.g. Mac OS X. + You'll have to restart Zope after this change. You'll then be able + to access the unthemed application on port 8080 like before and the + themed one on port 8081. + + Note that the Deliverance middleware requires lxml to do the + theming which is known to have problems on certain platforms, + e.g. Mac OS X. From philikon at codespeak.net Tue Mar 20 18:43:03 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Tue, 20 Mar 2007 18:43:03 +0100 (CET) Subject: [z3-checkins] r40852 - in z3/NudgeNudge/trunk/src/nudgenudge: macro_templates static Message-ID: <20070320174303.07FE110061@code0.codespeak.net> Author: philikon Date: Tue Mar 20 18:43:03 2007 New Revision: 40852 Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Log: Add menu Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Tue Mar 20 18:43:03 2007 @@ -4,6 +4,24 @@ + + +

+ Nudge Nudge +

+ -

-Nudge Nudge -

-
Body goes here Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Tue Mar 20 18:43:03 2007 @@ -4,5 +4,7 @@ + From ianb at codespeak.net Tue Mar 20 18:51:30 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Tue, 20 Mar 2007 18:51:30 +0100 (CET) Subject: [z3-checkins] r40854 - z3/deliverance/trunk/deliverance Message-ID: <20070320175130.6DFDA10061@code0.codespeak.net> Author: ianb Date: Tue Mar 20 18:51:29 2007 New Revision: 40854 Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py Log: When there's no content-type we *shouldn't* intercept; right now it *is* intercepting Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Tue Mar 20 18:51:29 2007 @@ -200,7 +200,7 @@ """ type = header_value(headers, 'content-type') if type is None: - return True # yerg, 304s can have no content-type + return False # yerg, 304s can have no content-type return type.startswith('text/html') or type.startswith('application/xhtml+xml') def filter_body(self, environ, body): From ianb at codespeak.net Tue Mar 20 18:53:46 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Tue, 20 Mar 2007 18:53:46 +0100 (CET) Subject: [z3-checkins] r40855 - z3/deliverance/trunk/deliverance Message-ID: <20070320175346.33DB110061@code0.codespeak.net> Author: ianb Date: Tue Mar 20 18:53:45 2007 New Revision: 40855 Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py Log: Added XMLHttpRequest check (using header supplied by Prototype.js) Modified: z3/deliverance/trunk/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/trunk/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/trunk/deliverance/wsgimiddleware.py Tue Mar 20 18:53:45 2007 @@ -156,6 +156,8 @@ environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) environ[DELIVERANCE_CACHE] = {} notheme = 'notheme' in qs + if environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest': + notheme = True if notheme: # eliminate the deliverance notheme query argument for the subrequest if qs == 'notheme': From ianb at codespeak.net Thu Mar 22 21:41:49 2007 From: ianb at codespeak.net (ianb at codespeak.net) Date: Thu, 22 Mar 2007 21:41:49 +0100 (CET) Subject: [z3-checkins] r41125 - z3/deliverance/trunk/deliverance Message-ID: <20070322204149.E529C10072@code0.codespeak.net> Author: ianb Date: Thu Mar 22 21:41:47 2007 New Revision: 41125 Modified: z3/deliverance/trunk/deliverance/interpreter.py Log: Check for strings when dropping elements, as strings don't need to be dropped but can get returned Modified: z3/deliverance/trunk/deliverance/interpreter.py ============================================================================== --- z3/deliverance/trunk/deliverance/interpreter.py (original) +++ z3/deliverance/trunk/deliverance/interpreter.py Thu Mar 22 21:41:47 2007 @@ -442,8 +442,10 @@ surrounding text """ removed = 0 - for el in els: - self.attach_text_to_previous(el,el.tail) + for el in els: + if isinstance(el, basestring): + continue + self.attach_text_to_previous(el, el.tail) el.getparent().remove(el) removed += 1 return removed From philikon at codespeak.net Fri Mar 23 16:39:59 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Fri, 23 Mar 2007 16:39:59 +0100 (CET) Subject: [z3-checkins] r41188 - in z3/NudgeNudge/trunk/src/nudgenudge: . app_templates Message-ID: <20070323153959.0D754100F2@code0.codespeak.net> Author: philikon Date: Fri Mar 23 16:39:57 2007 New Revision: 41188 Added: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/login.pt (contents, props changed) Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py Log: Use session credentials plug in and a custom login form (needs to be prettified) Modified: z3/NudgeNudge/trunk/src/nudgenudge/app.py ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/app.py (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/app.py Fri Mar 23 16:39:57 2007 @@ -1,5 +1,5 @@ import grok -from zope import schema, component +from zope import schema, component, interface from zope.app.intid import IntIds from zope.app.intid.interfaces import IIntIds from zope.app.catalog.catalog import Catalog @@ -7,7 +7,9 @@ from zope.app.authentication import PluggableAuthentication from zope.app.authentication.principalfolder import (PrincipalFolder, InternalPrincipal) +from zope.app.authentication.session import SessionCredentialsPlugin from zope.app.security.interfaces import IAuthentication +from zope.app.security.interfaces import IUnauthenticatedPrincipal from zope.app.securitypolicy.interfaces import IPrincipalRoleManager, IRole from zope.app.securitypolicy.interfaces import IRolePermissionManager from zope.app.securitypolicy.role import LocalRole @@ -25,10 +27,14 @@ catalog['package_name'] = ValueIndex('package_name', IReview, False) catalog['author'] = ValueIndex('author', IReview, False) -def setup_pau(pau): - pau['principals'] = principals = PrincipalFolder('nudge.principals.') +def setup_pau(pau): + pau['principals'] = PrincipalFolder('nudge.principals.') pau.authenticatorPlugins = ('principals',) + pau['session'] = session = SessionCredentialsPlugin() + session.loginpagename = 'login' + pau.credentialsPlugins = ('No Challenge if Authenticated', 'session',) + def role_factory(*args): def factory(): return LocalRole(*args) @@ -103,3 +109,12 @@ principals.prefix + email) self.redirect('index') + +class Login(grok.View): + grok.context(interface.Interface) + + def update(self, login_submit=None): + if (not IUnauthenticatedPrincipal.providedBy(self.request.principal) + and login_submit is not None): + camefrom = self.request.get('camefrom', '.') + self.redirect(camefrom) Added: z3/NudgeNudge/trunk/src/nudgenudge/app_templates/login.pt ============================================================================== --- (empty file) +++ z3/NudgeNudge/trunk/src/nudgenudge/app_templates/login.pt Fri Mar 23 16:39:57 2007 @@ -0,0 +1,22 @@ + + +
+ +Login + + + + + + + + + + + + + +
+ + From novalis at codespeak.net Fri Mar 23 23:00:44 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 23 Mar 2007 23:00:44 +0100 (CET) Subject: [z3-checkins] r41219 - z3/deliverance/branches/parallel Message-ID: <20070323220044.E93B81007A@code0.codespeak.net> Author: novalis Date: Fri Mar 23 23:00:43 2007 New Revision: 41219 Added: z3/deliverance/branches/parallel/ - copied from r41218, z3/deliverance/trunk/ Log: created branch From novalis at codespeak.net Fri Mar 23 23:01:26 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 23 Mar 2007 23:01:26 +0100 (CET) Subject: [z3-checkins] r41220 - in z3/deliverance/branches/parallel: . deliverance Message-ID: <20070323220126.E293C1007E@code0.codespeak.net> Author: novalis Date: Fri Mar 23 23:01:25 2007 New Revision: 41220 Modified: z3/deliverance/branches/parallel/ (props changed) z3/deliverance/branches/parallel/deliverance/test_wsgi.py z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py z3/deliverance/branches/parallel/setup.py Log: initial work on parallelism Modified: z3/deliverance/branches/parallel/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/test_wsgi.py (original) +++ z3/deliverance/branches/parallel/deliverance/test_wsgi.py Fri Mar 23 23:01:25 2007 @@ -225,6 +225,7 @@ status = res.status # make sure the response etag changed assert(header_value(res.headers, 'etag') != composite_etag) + print "status is", status assert(status == 200) # clear etags @@ -262,17 +263,94 @@ status = res.status assert(status == 200) - - - - +import time +class PausingMiddleware: + def __init__(self, app, sleep_time): + self.app = app + self.sleep_time = sleep_time + + def __call__(self, environ, start_response): + try: + time.sleep(self.sleep_time) + except KeyboardInterrupt: + return self.app(environ, start_response) + return self.app(environ, start_response) + + +from transcluder.tasklist import TaskList +the_tasklist = TaskList() +def test_parallel_gets(): + base_dir = os.path.dirname(__file__) + test_dir = os.path.join(base_dir, 'test-data', '304') + + sleep_time = 1 + cache_app = CacheFixtureApp() + sleep_app = PausingMiddleware(cache_app, sleep_time) + transcluder = DeliveranceMiddleware(sleep_app, '/theme.html', '/rules.xml', tasklist = the_tasklist) + static_test_app = TestApp(cache_app) + test_app = TestApp(transcluder) + + page_list = ['index.html', 'index2.html', 'page1.html', 'page2.html', 'page2_1.html', 'page3.html', 'page4.html', 'expected5.html'] + pages = {} + for page in page_list: + pages[page] = CacheFixtureResponseInfo(open(os.path.join(test_dir, page)).read()) + cache_app.map_url('/' + page, pages[page]) + pages[page].etag = page + + #load up the deptracker + start = time.time() + result = test_app.get('/index.html') + end = time.time() + #print "took %s sleep_times" % ((end - start) / sleep_time) + assert 2*sleep_time <= end - start < 3*sleep_time, the_tasklist.doprint(2, end - start) + + etag = header_value(result.headers, 'ETAG') + assert etag is not None + + #test parallel fetch from correct tracked deps + start = time.time() + result = test_app.get('/index.html', extra_environ={'HTTP_IF_NONE_MATCH' : etag}) + end = time.time() + #print "took %s sleep_times" % ((end - start) / sleep_time) + assert sleep_time <= end - start < 2*sleep_time, the_tasklist.doprint(1, end - start) + assert result.status == 304 + + pages['page1.html'].etag = 'page1.new' + start = time.time() + result = test_app.get('/index.html', extra_environ={'HTTP_IF_NONE_MATCH' : etag}) + end = time.time() + #print "took %s sleep_times" % ((end - start) / sleep_time) + + assert 2*sleep_time <= end - start < 3*sleep_time, the_tasklist.doprint(2, end - start) + etag = header_value(result.headers, 'ETAG') + + assert result.status == 200 + + # change the content of the index page, this will make it depend on page3 + cache_app.map_url('/index.html',pages['index2.html']) + start = time.time() + result = test_app.get('/index.html', extra_environ={'HTTP_IF_NONE_MATCH' : etag}) + end = time.time() + #print "took %s sleep_times" % ((end - start) / sleep_time) + assert 2*sleep_time <= end - start < 3*sleep_time, the_tasklist.doprint(2, end - start) + + # change dependency to have a dependency + cache_app.map_url('/page2.html', pages['page2_1.html']) + start = time.time() + result = test_app.get('/index.html', extra_environ={'HTTP_IF_NONE_MATCH' : etag}) + expected = static_test_app.get('/expected5.html') + html_string_compare(result.body, expected.body) + end = time.time() + #print "took %s sleep_times" % ((end - start) / sleep_time) + assert 2*sleep_time <= end - start < 3*sleep_time, the_tasklist.doprint(2, end - start) RENDERER_TYPES = ['py', 'xslt'] TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_cache ] +#TEST_FUNCS = [ do_url] def test_all(): for renderer_type in RENDERER_TYPES: for test_func in TEST_FUNCS: @@ -280,5 +358,7 @@ if __name__ == '__main__': + for x in test_all(): + x[0](*x[1:]) pass Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Fri Mar 23 23:01:25 2007 @@ -17,12 +17,16 @@ from deliverance.utils import DELIVERANCE_ERROR_PAGE from deliverance.resource_fetcher import InternalResourceFetcher, FileResourceFetcher, ExternalResourceFetcher from deliverance import cache_utils +from wsgifilter.resource_fetcher import * import sys import datetime import threading import traceback from StringIO import StringIO from sets import Set +from transcluder.tasklist import PageManager, TaskList +from transcluder.deptracker import DependencyTracker +from transcluder.cookie_wrapper import * DELIVERANCE_BASE_URL = 'deliverance.base-url' DELIVERANCE_CACHE = 'deliverance.cache' @@ -39,7 +43,7 @@ tranformation as a WSGI middleware component. """ - def __init__(self, app, theme_uri, rule_uri, renderer='py', merge_cache_control=False): + def __init__(self, app, theme_uri, rule_uri, renderer='py', merge_cache_control=False, deptracker = None, tasklist = None): """ initializer @@ -59,6 +63,16 @@ self.rule_uri = rule_uri self.merge_cache_control = merge_cache_control + if tasklist: + self.tasklist = tasklist + else: + self.tasklist = TaskList() + + if deptracker: + self.deptracker = deptracker + else: + self.deptracker = DependencyTracker() + if renderer == 'py': import interpreter self._rendererType = interpreter.Renderer @@ -108,6 +122,7 @@ try: parsedRule = etree.XML(rule) except Exception, message: + print "trying to parse %s" % rule message.public_html = 'Cannot parse rules (%s)' % message raise @@ -173,13 +188,40 @@ del environ['HTTP_IF_MATCH'] if 'HTTP_IF_UNMODIFIED_SINCE' in environ: del environ['HTTP_IF_UNMODIFIED_SINCE'] - + + + environ['transcluder.outcookies'] = {} + if environ.has_key('HTTP_COOKIE'): + environ['transcluder.incookies'] = expire_cookies(unwrap_cookies(environ['HTTP_COOKIE'])) + else: + environ['transcluder.incookies'] = {} + + + if environ.has_key('HTTP_IF_NONE_MATCH'): + print "etags are", cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + environ['transcluder.etags'] = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + else: + environ['transcluder.etags'] = {} + + old_get_resource = self.get_resource + get_resource = lambda url, env: old_get_resource(env, url) + + pm = PageManager(environ[DELIVERANCE_BASE_URL], environ, self.deptracker, lambda document, document_url: self.find_deps(environ, document, document_url), self.tasklist, self.etree_subrequest) + + def simple_fetch(env, url): + body = pm.fetch(url)[2] + return body + + self.get_resource = simple_fetch + status, headers, body = self.rebuild_check(environ, start_response) # non-html responses, or rebuild is not necessary: bail out if status is None: return body + pm.begin_speculative_gets() + # perform actual themeing body = self.filter_body(environ, body) @@ -191,8 +233,56 @@ headers, self.merge_cache_control) + start_response(status, headers) return [body] + + def is_html(self, status, headers): + type = header_value(headers, 'content-type') + return type and (type.startswith('text/html') or type.startswith('application/xhtml+xml')) + + def etree_subrequest(self, url, environ): + + url = urllib.unquote(url) + + url_parts = urlparse.urlparse(url) + env = environ.copy() + + env['PATH_INFO'] = url_parts[2] + if len(url_parts[4]): + env['QUERY_STRING'] = url_parts[4] + + request_url = construct_url(environ, with_path_info=False, with_query_string=False) + request_url_parts = urlparse.urlparse(request_url) + + #import pdb;pdb.set_trace() + + if request_url_parts[0:2] == url_parts[0:2]: + status, headers, body = get_internal_resource(url, env, self.app) + elif url_parts[0:2] == ('', ''): + status, headers, body = get_internal_resource(urlparse.urlunparse(request_url_parts[0:2] + url_parts[2:]), env, self.app) + else: + status, headers, body = get_external_resource(url, env) + + + + if status.startswith('200') and self.is_html(status, headers): + parsed = etree.HTML(body) + else: + parsed = None + return status, headers, body, parsed + + def find_deps(self, environ, document, document_url): + print "document url %s, dep url is %s" % (document_url, construct_url(environ)) + if document_url == construct_url(environ): + return [self.theme_uri, self.rule_uri] + elif document_url == self.theme_uri: + return [] + else: + #FIXME: check rules for xincludes. + return [] + + def should_intercept(self, status, headers): """ @@ -210,8 +300,8 @@ returns the result of the deliverance transformation on the string 'body' in the context of environ. The result is a string containing HTML. """ - content = self.get_renderer(environ).render(parseHTML(body)) + content = self.get_renderer(environ).render(parseHTML(body)) return tostring(content, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN", "http://www.w3.org/TR/html4/loose.dtd")) @@ -225,7 +315,6 @@ if 'HTTP_IF_NONE_MATCH' in environ: etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) tag = etag_map.get(content_url, None) - environ['HTTP_IF_NONE_MATCH'] = tag if tag: environ['HTTP_IF_NONE_MATCH'] = tag else: Modified: z3/deliverance/branches/parallel/setup.py ============================================================================== --- z3/deliverance/branches/parallel/setup.py (original) +++ z3/deliverance/branches/parallel/setup.py Fri Mar 23 23:01:25 2007 @@ -24,7 +24,10 @@ 'elementtree', 'nose', 'WSGIFilter', - 'setuptools' + 'setuptools', + 'enum', + 'pyavl', + 'decorator' ], include_package_data=True, entry_points=""" From philikon at codespeak.net Sat Mar 24 19:59:12 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Sat, 24 Mar 2007 19:59:12 +0100 (CET) Subject: [z3-checkins] r41255 - in z3/NudgeNudge/trunk/src/nudgenudge: macro_templates static Message-ID: <20070324185912.BDDC210074@code0.codespeak.net> Author: philikon Date: Sat Mar 24 19:59:07 2007 New Revision: 41255 Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Log: Login / logout box Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Sat Mar 24 19:59:07 2007 @@ -32,6 +32,36 @@
+
+

user name

+ + + + +
+
Body goes here Modified: z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/static/rules.xml Sat Mar 24 19:59:07 2007 @@ -7,4 +7,6 @@ + From kobold at codespeak.net Sun Mar 25 13:46:40 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sun, 25 Mar 2007 13:46:40 +0200 (CEST) Subject: [z3-checkins] r41274 - z3/sqlos/trunk/src/sqlos Message-ID: <20070325114640.C080A10080@code0.codespeak.net> Author: kobold Date: Sun Mar 25 13:46:40 2007 New Revision: 41274 Modified: z3/sqlos/trunk/src/sqlos/adapter.py Log: Filter Retry exceptions: this is required for serialized database connections. Modified: z3/sqlos/trunk/src/sqlos/adapter.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/adapter.py (original) +++ z3/sqlos/trunk/src/sqlos/adapter.py Sun Mar 25 13:46:40 2007 @@ -22,6 +22,7 @@ from sqlobject.converters import registerConverter from sqlobject.mysql import mysqlconnection from zope.rdb.interfaces import DatabaseException +from zope.publisher.interfaces import Retry from zope.app.container.interfaces import INameChooser from zope.interface import implements @@ -73,6 +74,8 @@ def _executeRetry(self, conn, cursor, query): try: return cursor.execute(query) + except Retry: + raise except Exception, exc: raise DatabaseException(str(exc.args)) @@ -84,6 +87,8 @@ except DatabaseException: raise # We may have already raised Database exception in # _executeRetry, so we re-raise it + except Retry: + raise except Exception, exc: raise DatabaseException, tuple(exc.args) finally: @@ -120,6 +125,8 @@ def _executeRetry(*args, **kw): try: return mysqlconnection.MySQLConnection._executeRetry(*args, **kw) + except Retry: + raise except Exception, exc: raise DatabaseException(str(exc.args)) From philikon at codespeak.net Mon Mar 26 22:06:41 2007 From: philikon at codespeak.net (philikon at codespeak.net) Date: Mon, 26 Mar 2007 22:06:41 +0200 (CEST) Subject: [z3-checkins] r41427 - z3/NudgeNudge/trunk/src/nudgenudge/macro_templates Message-ID: <20070326200641.C0DC310084@code0.codespeak.net> Author: philikon Date: Mon Mar 26 22:06:39 2007 New Revision: 41427 Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Log: Create a useful link in the left hand menu Modified: z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt ============================================================================== --- z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt (original) +++ z3/NudgeNudge/trunk/src/nudgenudge/macro_templates/master.pt Mon Mar 26 22:06:39 2007 @@ -11,8 +11,8 @@ tal:attributes="href request/getApplicationURL">Nudge Nudge From novalis at codespeak.net Tue Mar 27 23:38:22 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Tue, 27 Mar 2007 23:38:22 +0200 (CEST) Subject: [z3-checkins] r41552 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070327213822.116F51007A@code0.codespeak.net> Author: novalis Date: Tue Mar 27 23:38:21 2007 New Revision: 41552 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: another day another bug Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Tue Mar 27 23:38:21 2007 @@ -27,6 +27,7 @@ from transcluder.tasklist import PageManager, TaskList from transcluder.deptracker import DependencyTracker from transcluder.cookie_wrapper import * +from transcluder.middleware import is_conditional_get DELIVERANCE_BASE_URL = 'deliverance.base-url' DELIVERANCE_CACHE = 'deliverance.cache' @@ -207,18 +208,34 @@ get_resource = lambda url, env: old_get_resource(env, url) pm = PageManager(environ[DELIVERANCE_BASE_URL], environ, self.deptracker, lambda document, document_url: self.find_deps(environ, document, document_url), self.tasklist, self.etree_subrequest) - + self.pm = pm def simple_fetch(env, url): - body = pm.fetch(url)[2] + body = self.pm.fetch(url)[2] return body self.get_resource = simple_fetch - status, headers, body = self.rebuild_check(environ, start_response) + print "about to rebuild_check", construct_url(environ), environ.get('HTTP_IF_NONE_MATCH') + + #AHA! rebuild_check is calling fetch, which is always an unconditional + #request. I should be calling is_modified, but that probably will fail too. + + if is_conditional_get(environ) and not pm.is_modified(): + headers = [] + pm.merge_headers_into(headers) + start_response('304 Not Modified', headers) + return [] + + + status, headers, body, parsed = pm.fetch(construct_url(environ)) + + #status, headers, body = self.rebuild_check(environ, start_response) + + #print "got from rebuild_check", status # non-html responses, or rebuild is not necessary: bail out - if status is None: - return body +# if status is None: +# return body pm.begin_speculative_gets() @@ -272,6 +289,7 @@ parsed = None return status, headers, body, parsed + #fixme: is this the same as get_resource_uris? def find_deps(self, environ, document, document_url): print "document url %s, dep url is %s" % (document_url, construct_url(environ)) if document_url == construct_url(environ): @@ -312,20 +330,27 @@ content_url = construct_url(environ) etag_map = {} - if 'HTTP_IF_NONE_MATCH' in environ: - etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) - tag = etag_map.get(content_url, None) - if tag: - environ['HTTP_IF_NONE_MATCH'] = tag - else: - if 'HTTP_IF_NONE_MATCH' in environ: - del environ['HTTP_IF_NONE_MATCH'] - - - status, headers, body = intercept_output(environ, self.app, - self.should_intercept, - start_response) - + #I think this is all done above with the incookies stuff + #if 'HTTP_IF_NONE_MATCH' in environ: + # etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + # tag = etag_map.get(content_url, None) + # if tag: + # environ['HTTP_IF_NONE_MATCH'] = tag + # else: + # if 'HTTP_IF_NONE_MATCH' in environ: + # del environ['HTTP_IF_NONE_MATCH'] + + + #hm, fetch returned 200 instead of 304. Is this because of deps? + #no, I think it's because we fuck with the environ... no, we don't. + + status, headers, body, parsed = self.pm.fetch(content_url) + +# status, headers, body = intercept_output(environ, self.app, +# self.should_intercept, +# start_response) + #fixme: probably need to set status to none for non-html? + print "fetched %s, status is" % content_url, status if status is None: # should_intercept says this isn't HTML, we're done @@ -340,6 +365,7 @@ # it was modified or an error, give it back for themeing if not status.startswith('304'): + print "about to return for theming" # if it's not a full HTML page, skip it if not self.hasHTMLTag(body): start_response(status, headers) @@ -351,6 +377,7 @@ # got 304 Not Modified for content, check other resources rules = etree.XML(self.rule(environ)) resources = self.get_resource_uris(rules) + if self.any_modified(environ, resources, etag_map): # something changed, # get the content explicitly and give it back @@ -490,6 +517,7 @@ """ + print "CHECK_MOD", uri fetcher = self.get_fetcher(environ, uri) if httpdate_since: From novalis at codespeak.net Wed Mar 28 22:21:55 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Wed, 28 Mar 2007 22:21:55 +0200 (CEST) Subject: [z3-checkins] r41597 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070328202155.D9FA410075@code0.codespeak.net> Author: novalis Date: Wed Mar 28 22:21:44 2007 New Revision: 41597 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: fixed ajax Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Wed Mar 28 22:21:44 2007 @@ -229,6 +229,12 @@ status, headers, body, parsed = pm.fetch(construct_url(environ)) + #ajax + if not self.hasHTMLTag(body): + start_response(status, headers) + return [body] + + #status, headers, body = self.rebuild_check(environ, start_response) #print "got from rebuild_check", status @@ -240,7 +246,10 @@ pm.begin_speculative_gets() # perform actual themeing + old_body = body body = self.filter_body(environ, body) + if old_body == body: + print "no change in filter_body for", construct_url replace_header(headers, 'content-length', str(len(body))) replace_header(headers, 'content-type', 'text/html; charset=utf-8') @@ -291,7 +300,6 @@ #fixme: is this the same as get_resource_uris? def find_deps(self, environ, document, document_url): - print "document url %s, dep url is %s" % (document_url, construct_url(environ)) if document_url == construct_url(environ): return [self.theme_uri, self.rule_uri] elif document_url == self.theme_uri: From timte at codespeak.net Thu Mar 29 11:30:15 2007 From: timte at codespeak.net (timte at codespeak.net) Date: Thu, 29 Mar 2007 11:30:15 +0200 (CEST) Subject: [z3-checkins] r41610 - in z3/CMFonFive/trunk: . tests/products/CMFonFiveTest tests/products/CMFonFiveTest/tests Message-ID: <20070329093015.38AD910076@code0.codespeak.net> Author: timte Date: Thu Mar 29 11:30:11 2007 New Revision: 41610 Modified: z3/CMFonFive/trunk/fiveactionstool.py z3/CMFonFive/trunk/tests/products/CMFonFiveTest/configure.zcml z3/CMFonFive/trunk/tests/products/CMFonFiveTest/tests/test_actionstool.py Log: Adding menu items with for="*" caused AttributeError. Added a check for this condition. If for contains no interface zope.interface.Interface is used for constructing the action identifier. Modified: z3/CMFonFive/trunk/fiveactionstool.py ============================================================================== --- z3/CMFonFive/trunk/fiveactionstool.py (original) +++ z3/CMFonFive/trunk/fiveactionstool.py Thu Mar 29 11:30:11 2007 @@ -21,7 +21,7 @@ from Products.CMFCore.Expression import createExprContext, Expression from Products.CMFCore.utils import UniqueObject, getToolByName -from zope.interface import providedBy +from zope.interface import providedBy, Interface from zope.app import zapi from zope.app.pagetemplate import engine from zope.app.publisher.interfaces.browser import IBrowserMenu @@ -94,7 +94,11 @@ res = [] for index, order, title, item in result: - identifier = '%s_%s' % (item._for.__identifier__.split('.')[-1], + if item._for: + for_ = item._for + else: + for_ = Interface + identifier = '%s_%s' % (for_.__identifier__.split('.')[-1], item.action.split('/')[-1]) identifier = identifier.replace(' ', '_').lower() res.append({'title': item.title, Modified: z3/CMFonFive/trunk/tests/products/CMFonFiveTest/configure.zcml ============================================================================== --- z3/CMFonFive/trunk/tests/products/CMFonFiveTest/configure.zcml (original) +++ z3/CMFonFive/trunk/tests/products/CMFonFiveTest/configure.zcml Thu Mar 29 11:30:11 2007 @@ -28,6 +28,15 @@ permission="zope2.ManageUsers" /> + + Modified: z3/CMFonFive/trunk/tests/products/CMFonFiveTest/tests/test_actionstool.py ============================================================================== --- z3/CMFonFive/trunk/tests/products/CMFonFiveTest/tests/test_actionstool.py (original) +++ z3/CMFonFive/trunk/tests/products/CMFonFiveTest/tests/test_actionstool.py Thu Mar 29 11:30:11 2007 @@ -37,8 +37,11 @@ # But not the protected action: self.failIf('icmfcontent_protected.html' in action_names, 'Protected menu item was found in action list') - # And there should be no actions anywhere else: - self.failUnlessEqual(list(tool.listActions(object=self.folder)), []) + # And there should be one action that is there for any object + actions_for_any = list(tool.listActions(object=self.folder)) + self.failUnlessEqual(len(actions_for_any), 1) + self.failUnless('interface_public_any.html' in action_names, + 'Expected menu item was not found in action list') def test_suite(): From novalis at codespeak.net Thu Mar 29 22:58:00 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Thu, 29 Mar 2007 22:58:00 +0200 (CEST) Subject: [z3-checkins] r41656 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070329205800.35F2010090@code0.codespeak.net> Author: novalis Date: Thu Mar 29 22:57:58 2007 New Revision: 41656 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: more tests pass Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Thu Mar 29 22:57:58 2007 @@ -17,6 +17,7 @@ from deliverance.utils import DELIVERANCE_ERROR_PAGE from deliverance.resource_fetcher import InternalResourceFetcher, FileResourceFetcher, ExternalResourceFetcher from deliverance import cache_utils +from wsgifilter.cache_utils import parse_merged_etag #this version must be a bit difference than the deli version from wsgifilter.resource_fetcher import * import sys import datetime @@ -199,34 +200,26 @@ if environ.has_key('HTTP_IF_NONE_MATCH'): - print "etags are", cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) - environ['transcluder.etags'] = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) + environ['transcluder.etags'] = parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) else: environ['transcluder.etags'] = {} old_get_resource = self.get_resource get_resource = lambda url, env: old_get_resource(env, url) - pm = PageManager(environ[DELIVERANCE_BASE_URL], environ, self.deptracker, lambda document, document_url: self.find_deps(environ, document, document_url), self.tasklist, self.etree_subrequest) + pm = PageManager(construct_url(environ), environ, self.deptracker, lambda document, document_url: self.find_deps(environ, document, document_url), self.tasklist, self.etree_subrequest) self.pm = pm def simple_fetch(env, url): body = self.pm.fetch(url)[2] return body self.get_resource = simple_fetch - - print "about to rebuild_check", construct_url(environ), environ.get('HTTP_IF_NONE_MATCH') - - #AHA! rebuild_check is calling fetch, which is always an unconditional - #request. I should be calling is_modified, but that probably will fail too. - if is_conditional_get(environ) and not pm.is_modified(): headers = [] pm.merge_headers_into(headers) start_response('304 Not Modified', headers) return [] - status, headers, body, parsed = pm.fetch(construct_url(environ)) #ajax @@ -234,30 +227,21 @@ start_response(status, headers) return [body] - - #status, headers, body = self.rebuild_check(environ, start_response) - - #print "got from rebuild_check", status - - # non-html responses, or rebuild is not necessary: bail out -# if status is None: -# return body - pm.begin_speculative_gets() # perform actual themeing old_body = body body = self.filter_body(environ, body) - if old_body == body: - print "no change in filter_body for", construct_url replace_header(headers, 'content-length', str(len(body))) replace_header(headers, 'content-type', 'text/html; charset=utf-8') - cache_utils.merge_cache_headers(environ, - environ[DELIVERANCE_CACHE], - headers, - self.merge_cache_control) + pm.merge_headers_into(headers) + +# cache_utils.merge_cache_headers(environ, +# environ[DELIVERANCE_CACHE], +# headers, +# self.merge_cache_control) start_response(status, headers) @@ -358,7 +342,6 @@ # self.should_intercept, # start_response) #fixme: probably need to set status to none for non-html? - print "fetched %s, status is" % content_url, status if status is None: # should_intercept says this isn't HTML, we're done @@ -373,7 +356,6 @@ # it was modified or an error, give it back for themeing if not status.startswith('304'): - print "about to return for theming" # if it's not a full HTML page, skip it if not self.hasHTMLTag(body): start_response(status, headers) From novalis at codespeak.net Thu Mar 29 23:59:53 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Thu, 29 Mar 2007 23:59:53 +0200 (CEST) Subject: [z3-checkins] r41658 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070329215953.EFCB310090@code0.codespeak.net> Author: novalis Date: Thu Mar 29 23:59:52 2007 New Revision: 41658 Modified: z3/deliverance/branches/parallel/deliverance/interpreter.py z3/deliverance/branches/parallel/deliverance/test_wsgi.py z3/deliverance/branches/parallel/deliverance/utils.py z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py z3/deliverance/branches/parallel/deliverance/xslt.py Log: floating point exception Modified: z3/deliverance/branches/parallel/deliverance/interpreter.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/interpreter.py (original) +++ z3/deliverance/branches/parallel/deliverance/interpreter.py Thu Mar 29 23:59:52 2007 @@ -152,6 +152,9 @@ content_els = copy.deepcopy( content.xpath(self.get_content_xpath(rule))) + + print "apply_append", rule, theme, len(content_els), content_els + if len(content_els) == 0: if rule.get(self.NOCONTENT_KEY) != 'ignore': self.add_to_body_start( Modified: z3/deliverance/branches/parallel/deliverance/test_wsgi.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/test_wsgi.py (original) +++ z3/deliverance/branches/parallel/deliverance/test_wsgi.py Thu Mar 29 23:59:52 2007 @@ -225,7 +225,6 @@ status = res.status # make sure the response etag changed assert(header_value(res.headers, 'etag') != composite_etag) - print "status is", status assert(status == 200) # clear etags @@ -350,7 +349,7 @@ RENDERER_TYPES = ['py', 'xslt'] TEST_FUNCS = [ do_url, do_basic, do_text, do_tasktracker, do_xinclude, do_with_spaces, do_nycsr, do_necoro, do_guidesearch, do_ajax, do_aggregate, do_cache ] -#TEST_FUNCS = [ do_url] +TEST_FUNCS = [do_aggregate] def test_all(): for renderer_type in RENDERER_TYPES: for test_func in TEST_FUNCS: Modified: z3/deliverance/branches/parallel/deliverance/utils.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/utils.py (original) +++ z3/deliverance/branches/parallel/deliverance/utils.py Thu Mar 29 23:59:52 2007 @@ -447,6 +447,11 @@ gets the xpath to lookup the content referred to by rule in the aggregated content document """ + + import htmlserialize + + print "xpath for rule %s" % htmlserialize.tostring(rule) + content_xpath = rule.get(self.RULE_CONTENT_KEY) if content_xpath is None: Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Thu Mar 29 23:59:52 2007 @@ -169,6 +169,7 @@ using the transformation specified in the initializer. """ + qs = environ.get('QUERY_STRING', '') environ[DELIVERANCE_BASE_URL] = construct_url(environ, with_path_info=False, with_query_string=False) environ[DELIVERANCE_CACHE] = {} Modified: z3/deliverance/branches/parallel/deliverance/xslt.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/xslt.py (original) +++ z3/deliverance/branches/parallel/deliverance/xslt.py Thu Mar 29 23:59:52 2007 @@ -160,6 +160,8 @@ """ prepare transform elements for "append" rule """ + + print "apply_append", rule, theme theme_el = self.get_theme_el(rule, theme) if theme_el is None: return From novalis at codespeak.net Fri Mar 30 20:44:39 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 30 Mar 2007 20:44:39 +0200 (CEST) Subject: [z3-checkins] r41732 - z3/deliverance/branches/parallel/deliverance/test-data/304 Message-ID: <20070330184439.4D53A10079@code0.codespeak.net> Author: novalis Date: Fri Mar 30 20:44:37 2007 New Revision: 41732 Added: z3/deliverance/branches/parallel/deliverance/test-data/304/ z3/deliverance/branches/parallel/deliverance/test-data/304/expected5.html z3/deliverance/branches/parallel/deliverance/test-data/304/index.html z3/deliverance/branches/parallel/deliverance/test-data/304/index2.html z3/deliverance/branches/parallel/deliverance/test-data/304/page1.html z3/deliverance/branches/parallel/deliverance/test-data/304/page2.html z3/deliverance/branches/parallel/deliverance/test-data/304/page2_1.html z3/deliverance/branches/parallel/deliverance/test-data/304/page3.html z3/deliverance/branches/parallel/deliverance/test-data/304/page4.html Log: added test data Added: z3/deliverance/branches/parallel/deliverance/test-data/304/expected5.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/expected5.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,14 @@ + + + + + +April is the cruelest month, breeding + + +October is the strangest month, brooding + +Memory and desire, stirring +Dull roots with spring rain. + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/index.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/index.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,10 @@ + + + + +page1 +page2 +Memory and desire, stirring +Dull roots with spring rain. + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/index2.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/index2.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,10 @@ + + + + +page1 +page3 +Memory and desire, stirring +Dull roots with spring rain. + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/page1.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/page1.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,7 @@ + + + + +April is the cruelest month, breeding + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/page2.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/page2.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,7 @@ + + + + +Lilacs out of the dead land, mixing + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/page2_1.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/page2_1.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,8 @@ + + + + +Lilacs out of the dead land, mixing + + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/page3.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/page3.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,7 @@ + + + + +October is the strangest month, brooding + + Added: z3/deliverance/branches/parallel/deliverance/test-data/304/page4.html ============================================================================== --- (empty file) +++ z3/deliverance/branches/parallel/deliverance/test-data/304/page4.html Fri Mar 30 20:44:37 2007 @@ -0,0 +1,7 @@ + + + + +June is the longest month, except for September + + From novalis at codespeak.net Fri Mar 30 21:22:14 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 30 Mar 2007 21:22:14 +0200 (CEST) Subject: [z3-checkins] r41733 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070330192214.D36151007B@code0.codespeak.net> Author: novalis Date: Fri Mar 30 21:22:11 2007 New Revision: 41733 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: find_deps now follows hrefs, I think Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Fri Mar 30 21:22:11 2007 @@ -284,14 +284,15 @@ return status, headers, body, parsed #fixme: is this the same as get_resource_uris? - def find_deps(self, environ, document, document_url): + def find_deps(self, environ, document, document_url): if document_url == construct_url(environ): return [self.theme_uri, self.rule_uri] elif document_url == self.theme_uri: return [] - else: + elif document_url == self.rules_uri: + return self.get_resource_uris(document) #FIXME: check rules for xincludes. - return [] + return [] From ltucker at codespeak.net Fri Mar 30 21:28:56 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Fri, 30 Mar 2007 21:28:56 +0200 (CEST) Subject: [z3-checkins] r41735 - in z3/deliverance/branches/parallel: . deliverance Message-ID: <20070330192856.C38C11007A@code0.codespeak.net> Author: ltucker Date: Fri Mar 30 21:28:55 2007 New Revision: 41735 Removed: z3/deliverance/branches/parallel/deliverance/resource_fetcher.py Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py z3/deliverance/branches/parallel/setup.py Log: removing old style mod checking and fetching Deleted: /z3/deliverance/branches/parallel/deliverance/resource_fetcher.py ============================================================================== --- /z3/deliverance/branches/parallel/deliverance/resource_fetcher.py Fri Mar 30 21:28:55 2007 +++ (empty file) @@ -1,181 +0,0 @@ -import urllib -import deliverance.wsgimiddleware -from StringIO import StringIO -from paste.wsgilib import intercept_output -from paste.proxy import TransparentProxy -from paste.request import construct_url -from paste.response import header_value -from paste.fileapp import FileApp -import urlparse -from deliverance.utils import DeliveranceError - - -class InternalResourceFetcher(object): - def __init__(self, in_environ, uri, app, headers_only=False): - self.uri = uri - self.app = app - - if 'paste.recursive.include' in in_environ: - self.environ = in_environ['paste.recursive.include'].original_environ.copy() - self.environ['paste.recursive.include'] = in_environ['paste.recursive.include'] - else: - self.environ = in_environ.copy() - - if not self.uri.startswith('/'): - self.uri = '/' + self.uri - - uri_parts = urlparse.urlparse(uri) - - self.environ['PATH_INFO'] = urllib.unquote(uri_parts[2]) - if len(uri_parts[4]) > 0: - self.environ['QUERY_STRING'] = uri_parts[4] + '¬heme' - else: - self.environ['QUERY_STRING'] = 'notheme' - - base_url = in_environ['deliverance.base-url'] - if base_url is not None: - self.environ['SCRIPT_NAME'] = urllib.unquote(urlparse.urlparse(base_url)[2]) - else: - self.environ['SCRIPT_NAME'] = '' - - if headers_only: - self.environ['REQUEST_METHOD'] = 'HEAD' - else: - self.environ['REQUEST_METHOD'] = 'GET' - - self.environ['CONTENT_LENGTH'] = '0' - self.environ['wsgi.input'] = StringIO('') - self.environ['CONTENT_TYPE'] = '' - - - if 'HTTP_ACCEPT_ENCODING' in self.environ: - self.environ['HTTP_ACCEPT_ENCODING'] = '' - - def wsgi_get(self): - if 'paste.recursive.include' in self.environ: - # Try to do the redirect this way... - includer = self.environ['paste.recursive.include'] - return intercept_output(self.environ, includer.application) - else: - status, headers, body = intercept_output(self.environ, self.app) - return (status, headers, body) - - - def get(self): - path_info = self.environ['PATH_INFO'] - status, headers, body = self.wsgi_get() - - if not status.startswith('200'): - loc = header_value(headers, 'location') - if loc: - loc = ' location=%r' % loc - else: - loc = '' - raise DeliveranceError( - "Request for internal resource at %s (%r) failed with status code %r%s" - % (construct_url(self.environ), path_info, status, - loc)) - return body - -class FileResourceFetcher(object): - def __init__(self, environ, uri, headers_only=False): - self.environ = environ.copy() - self.uri = uri - - uri_parts = urlparse.urlparse(self.uri) - self.environ['PATH_INFO'] = uri_parts[2] - self.environ['SCRIPT_INFO'] = '' - self.environ['wsgi.scheme'] = 'file' - if len(uri_parts[4]) > 0: - self.environ['QUERY_STRING'] = uri_parts[4] + '¬heme' - else: - self.environ['QUERY_STRING'] = 'notheme' - - if headers_only: - self.environ['REQUEST_METHOD'] = 'HEAD' - else: - self.environ['REQUEST_METHOD'] = 'GET' - - self.environ['CONTENT_LENGTH'] = '0' - self.environ['wsgi.input'] = StringIO('') - self.environ['CONTENT_TYPE'] = '' - - if 'HTTP_ACCEPT_ENCODING' in self.environ: - del self.environ['HTTP_ACCEPT_ENCODING'] - - def wsgi_get(self): - path = urlparse.urlparse(self.uri)[2] - file_app = FileApp(path) - - return intercept_output(self.environ, file_app) - - - def get(self): - path_info = self.environ['PATH_INFO'] - status, headers, body = self.wsgi_get() - - if not status.startswith('200'): - loc = header_value(headers, 'location') - if loc: - loc = ' location=%r' % loc - else: - loc = '' - raise DeliveranceError( - "Request for file at %s (%r) failed with status code %r%s" - % (construct_url(self.environ), path_info, status, - loc)) - return body - -class ExternalResourceFetcher(object): - def __init__(self, uri, headers_only=False): - self.uri = uri - - url_chunks = urlparse.urlsplit(uri) - loc = urlparse.urlsplit(uri) - - self.environ = {} - - if headers_only: - self.environ['REQUEST_METHOD'] = 'HEAD' - else: - self.environ['REQUEST_METHOD'] = 'GET' - - self.environ['CONTENT_LENGTH'] = '0' - self.environ['wsgi.input'] = StringIO('') - - self.environ['wsgi.url_scheme'] = loc[0] - self.environ['wsgi.version'] = (1, 0) - self.environ['HTTP_HOST'] = loc[1] - self.environ['PATH_INFO'] = loc[2] - self.environ['QUERY_STRING'] = loc[3] - - self.environ['SCRIPT_INFO'] = '' - - #if loc[0].find(':') != -1: - # self.environ['SERVER_NAME'],self.environ['SERVER_PORT'] = loc[0].split(':') - #else: - # self.environ['SERVER_NAME'] = loc[0] - # if loc[0] == 'https': - # self.environ['SERVER_PORT'] = '443' - # else: - # self.environ['SERVER_PORT'] = '80' - - def wsgi_get(self): - proxy_app = TransparentProxy() - return intercept_output(self.environ, proxy_app) - - def get(self): - status, headers, body = self.wsgi_get() - - if not status.startswith('200'): - loc = header_value(headers, 'location') - if loc: - loc = ' location=%r' % loc - else: - loc = '' - raise DeliveranceError( - "Request for external resource at %s failed with status code %r%s" - % (construct_url(self.environ), status, - loc)) - - return body Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Fri Mar 30 21:28:55 2007 @@ -86,48 +86,86 @@ else: self._rendererType = renderer - def get_renderer(self, environ): - return self.create_renderer(environ) + def get_rules(self, fetch): + try: + status, headers, body, parsed = fetch(self.rule_uri) + if not status.startswith('200'): + raise DeliveranceError("Unable to retrieve rules from %s (status = %s)" % (self.rule_uri, status)) + + except Exception, message: + newmessage = "Unable to retrieve rules from %s " % self.rule_uri + if message: + newmessage += ": " + str(message) + + raise DeliveranceError(newmessage) + + try: + parsed_rules = etree.XML(body) + except Exception, message: + message.public_html = 'Cannot parse rules (%s) [%s]' % (message, rule) + raise - def create_renderer(self, environ): + return parsed_rules + + def get_theme(self, fetch): + try: + status, headers, body, parsed = fetch(self.theme_uri) + if not status.startswith('200'): + raise DeliveranceError("Unable to retrieve theme from %s (status = %s)" % (self.rule_uri, status)) + return parsed + except Exception, message: + message.public_html = 'Unable to retrieve theme page from %s: %s' % ( + self.theme_uri, message) + raise + + def create_renderer(self, environ, page_manager): """ construct a new deliverance Renderer from the information passed to the initializer. A new copy of the theme and rules is retrieved. """ - theme = self.theme(environ) - rule = self.rule(environ) - full_theme_uri = urlparse.urljoin( - construct_url(environ, with_path_info=False), - self.theme_uri) def reference_resolver(href, parse, encoding=None): - text = self.get_resource(environ, href) + status, headers, body, parsed = page_manager.fetch(href) + + if not status.startswith('200'): + path_info = uri + loc = header_value(headers, 'location') + if loc: + loc = ' location=%r' % loc + else: + loc = '' + raise DeliveranceError( + "Request for internal resource at %s (%r) failed with status code %r%s" + % (construct_url(environ), path_info, status, + loc)) + + if parse == "html": + return parsed if parse == "xml": return etree.XML(text) - if parse == "html": - return etree.HTML(text) else: if encoding: return text.decode(encoding) else: return text + + full_theme_uri = urlparse.urljoin( + construct_url(environ, with_path_info = False), + self.theme_uri) + + parsedTheme = self.get_theme(page_manager.fetch) + parsedRule = self.get_rules(page_manager.fetch) + try: - parsedTheme = parseHTML(theme) + parsedRule = etree.XML(page_manager.fetch(self.rule_uri)[2]) except Exception, message: - newmessage = "Unable to parse theme page (" + self.theme_uri + ")" + newmessage = "Unable to retrieve rules from " + self.rule_uri if message: - newmessage += ":" + str(message) + newmessage += ": " + str(message) raise DeliveranceError(newmessage) - - try: - parsedRule = etree.XML(rule) - except Exception, message: - print "trying to parse %s" % rule - message.public_html = 'Cannot parse rules (%s)' % message - raise - + return self._rendererType( theme=parsedTheme, theme_uri=full_theme_uri, @@ -135,32 +173,6 @@ rule_uri=self.rule_uri, reference_resolver=reference_resolver) - def rule(self, environ): - """ - retrieves the data referred to by the rule_uri passed to the - initializer. - """ - try: - return self.get_resource(environ, self.rule_uri) - except Exception, message: - newmessage = "Unable to retrieve rules from " + self.rule_uri - if message: - newmessage += ": " + str(message) - - raise DeliveranceError(newmessage) - - def theme(self, environ): - """ - retrieves the data referred to by the theme_uri passed to the - initializer. - """ - try: - return self.get_resource(environ, self.theme_uri) - except Exception, message: - message.public_html = 'Unable to retrieve theme page from %s: %s' % ( - self.theme_uri, message) - raise - def __call__(self, environ, start_response): """ WSGI entrypoint, responds to the request in @@ -205,16 +217,12 @@ else: environ['transcluder.etags'] = {} - old_get_resource = self.get_resource - get_resource = lambda url, env: old_get_resource(env, url) - pm = PageManager(construct_url(environ), environ, self.deptracker, lambda document, document_url: self.find_deps(environ, document, document_url), self.tasklist, self.etree_subrequest) + pm = PageManager(construct_url(environ), environ, self.deptracker, + lambda document, document_url: self.find_deps(environ, document, document_url), + self.tasklist, self.etree_subrequest) self.pm = pm - def simple_fetch(env, url): - body = self.pm.fetch(url)[2] - return body - self.get_resource = simple_fetch if is_conditional_get(environ) and not pm.is_modified(): headers = [] pm.merge_headers_into(headers) @@ -230,21 +238,17 @@ pm.begin_speculative_gets() - # perform actual themeing - old_body = body - body = self.filter_body(environ, body) + # perform actual themeing + + themed_doc = self.create_renderer(environ, pm).render(parsed) + body = tostring(themed_doc, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN", + "http://www.w3.org/TR/html4/loose.dtd")) replace_header(headers, 'content-length', str(len(body))) replace_header(headers, 'content-type', 'text/html; charset=utf-8') pm.merge_headers_into(headers) -# cache_utils.merge_cache_headers(environ, -# environ[DELIVERANCE_CACHE], -# headers, -# self.merge_cache_control) - - start_response(status, headers) return [body] @@ -307,177 +311,6 @@ return False # yerg, 304s can have no content-type return type.startswith('text/html') or type.startswith('application/xhtml+xml') - def filter_body(self, environ, body): - """ - returns the result of the deliverance transformation on the string 'body' - in the context of environ. The result is a string containing HTML. - """ - - content = self.get_renderer(environ).render(parseHTML(body)) - return tostring(content, doctype_pair=("-//W3C//DTD HTML 4.01 Transitional//EN", - "http://www.w3.org/TR/html4/loose.dtd")) - - - def rebuild_check(self, environ, start_response): - # perform the request for content - - content_url = construct_url(environ) - - etag_map = {} - #I think this is all done above with the incookies stuff - #if 'HTTP_IF_NONE_MATCH' in environ: - # etag_map = cache_utils.parse_merged_etag(environ['HTTP_IF_NONE_MATCH']) - # tag = etag_map.get(content_url, None) - # if tag: - # environ['HTTP_IF_NONE_MATCH'] = tag - # else: - # if 'HTTP_IF_NONE_MATCH' in environ: - # del environ['HTTP_IF_NONE_MATCH'] - - - #hm, fetch returned 200 instead of 304. Is this because of deps? - #no, I think it's because we fuck with the environ... no, we don't. - - status, headers, body, parsed = self.pm.fetch(content_url) - -# status, headers, body = intercept_output(environ, self.app, -# self.should_intercept, -# start_response) - #fixme: probably need to set status to none for non-html? - - if status is None: - # should_intercept says this isn't HTML, we're done - return (None, None, body) - - if self.should_ignore_url(content_url): - start_response(status, headers) - return (None, None, [body]) - - # cache the response so we can look at its headers later - environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) - - # it was modified or an error, give it back for themeing - if not status.startswith('304'): - # if it's not a full HTML page, skip it - if not self.hasHTMLTag(body): - start_response(status, headers) - return (None, None, [body]) - - # send it back for rebuild - return (status, headers, body) - - # got 304 Not Modified for content, check other resources - rules = etree.XML(self.rule(environ)) - resources = self.get_resource_uris(rules) - - if self.any_modified(environ, resources, etag_map): - # something changed, - # get the content explicitly and give it back - if 'HTTP_IF_MODIFIED_SINCE' in environ: - del environ['HTTP_IF_MODIFIED_SINCE'] - if 'HTTP_IF_NONE_MATCH' in environ: - del environ['HTTP_IF_NONE_MATCH'] - environ['CACHE-CONTROL'] = 'no-cache' - - status, headers, body = intercept_output(environ, self.app) - - if not self.hasHTMLTag(body): - # XXX yarg, we didn't care about it! - start_response(status, headers) - return (None, None, [body]) - - environ[DELIVERANCE_CACHE][content_url] = (status, headers, body) - return (status, headers, body) - - # nothing was modified, give back a 304 - cache_utils.merge_cache_headers(environ, - environ[DELIVERANCE_CACHE], - headers, - self.merge_cache_control) - start_response('304 Not Modified', headers) - - return (None,None,[]) - - def any_modified(self, environ, resources, etag_map): - """ - returns a boolean indicating whether any of the uris in the resources - list have been modified. if an entry for the uri exists in the map - etag_map, the value will be used to check the resource using an - if-none-match http header. if an if-not-modified check is desired, - it should be present in environ. - """ - moddate = None - - if 'HTTP_IF_MODIFIED_SINCE' in environ: - moddate = environ['HTTP_IF_MODIFIED_SINCE'] - - for uri in resources: - if (self.check_modification(environ, uri, - moddate, - etag_map.get(uri,None))): - return True - - return False - - - def get_resource(self, environ, uri): - """ - retrieve the content from the uri given, - uses cache if possible. throws exception if - response is not 200 - """ - if uri in environ[DELIVERANCE_CACHE]: - response = environ[DELIVERANCE_CACHE][uri] - if response[0].startswith('200'): - return response[2] - - fetcher = self.get_fetcher(environ, uri) - - - # eliminate validation headers, we want the content - if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: - del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] - if 'HTTP_IF_NONE_MATCH' in fetcher.environ: - del fetcher.environ['HTTP_IF_NONE_MATCH'] - fetcher.environ['CACHE-CONTROL'] = 'no-cache' - - - status, headers, body = fetcher.wsgi_get() - - if not status.startswith('200'): - path_info = uri - loc = header_value(headers, 'location') - if loc: - loc = ' location=%r' % loc - else: - loc = '' - raise DeliveranceError( - "Request for internal resource at %s (%r) failed with status code %r%s" - % (construct_url(environ), path_info, status, - loc)) - - environ[DELIVERANCE_CACHE][uri] = (status, headers, body) - - return body - - - def get_fetcher(self, environ, uri): - """ - retrieve an object which is appropriate for fetching the - uri specified. - """ - internalBaseURL = environ.get(DELIVERANCE_BASE_URL,None) - uri = urlparse.urljoin(internalBaseURL, uri) - - if urlparse.urlparse(uri)[0] == 'file': - return FileResourceFetcher(environ, uri) - - elif internalBaseURL and uri.startswith(internalBaseURL): - return InternalResourceFetcher(environ, uri[len(internalBaseURL):], - self.app) - else: - return ExternalResourceFetcher(uri) - def get_resource_uris(self, rules): """ @@ -496,46 +329,6 @@ return list(resources) - def check_modification(self, environ, uri, httpdate_since=None, etag=None): - """ - if httpdate_since is set to an httpdate the If-Modified-Since HTTP header - is used to check for modification - - if etag is set to an etag for the resource, the If-None-Match HTTP header - is used to check for modification - - the resulting (status, headers, body) tuple for the request is stored in - environ[DELIVERANCE_CACHE][uri]. - - """ - - print "CHECK_MOD", uri - fetcher = self.get_fetcher(environ, uri) - - if httpdate_since: - fetcher.environ['HTTP_IF_MODIFIED_SINCE'] = httpdate_since - else: - if 'HTTP_IF_MODIFIED_SINCE' in fetcher.environ: - del fetcher.environ['HTTP_IF_MODIFIED_SINCE'] - - - if etag: - fetcher.environ['HTTP_IF_NONE_MATCH'] = etag - else: - if 'HTTP_IF_NONE_MATCH' in fetcher.environ: - del fetcher.environ['HTTP_IF_NONE_MATCH'] - - - status, headers, body = fetcher.wsgi_get() - environ[DELIVERANCE_CACHE][uri] = (status, headers, body) - - if status.startswith('304'): # Not Modified - return False - - return True - - - HTML_DOC_PAT = re.compile(r"^.*<\s*html(\s*|>).*$",re.I|re.M) def hasHTMLTag(self, body): """ Modified: z3/deliverance/branches/parallel/setup.py ============================================================================== --- z3/deliverance/branches/parallel/setup.py (original) +++ z3/deliverance/branches/parallel/setup.py Fri Mar 30 21:28:55 2007 @@ -20,6 +20,7 @@ install_requires=[ 'lxml>=1.2', 'Paste', + 'PasteScript', 'FormEncode', 'elementtree', 'nose', From novalis at codespeak.net Fri Mar 30 22:12:48 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 30 Mar 2007 22:12:48 +0200 (CEST) Subject: [z3-checkins] r41738 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070330201248.DACA71007A@code0.codespeak.net> Author: novalis Date: Fri Mar 30 22:12:45 2007 New Revision: 41738 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: oops Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Fri Mar 30 22:12:45 2007 @@ -293,7 +293,7 @@ return [self.theme_uri, self.rule_uri] elif document_url == self.theme_uri: return [] - elif document_url == self.rules_uri: + elif document_url == self.rule_uri: return self.get_resource_uris(document) #FIXME: check rules for xincludes. return [] From novalis at codespeak.net Fri Mar 30 22:27:50 2007 From: novalis at codespeak.net (novalis at codespeak.net) Date: Fri, 30 Mar 2007 22:27:50 +0200 (CEST) Subject: [z3-checkins] r41739 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070330202750.F312D1007A@code0.codespeak.net> Author: novalis Date: Fri Mar 30 22:27:47 2007 New Revision: 41739 Modified: z3/deliverance/branches/parallel/deliverance/interpreter.py z3/deliverance/branches/parallel/deliverance/utils.py z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py z3/deliverance/branches/parallel/deliverance/xinclude.py z3/deliverance/branches/parallel/deliverance/xslt.py Log: individual parsers (even though it does not fix anything) Modified: z3/deliverance/branches/parallel/deliverance/interpreter.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/interpreter.py (original) +++ z3/deliverance/branches/parallel/deliverance/interpreter.py Fri Mar 30 22:27:47 2007 @@ -153,7 +153,7 @@ content.xpath(self.get_content_xpath(rule))) - print "apply_append", rule, theme, len(content_els), content_els + #print "apply_append", rule, theme, len(content_els), content_els if len(content_els) == 0: if rule.get(self.NOCONTENT_KEY) != 'ignore': Modified: z3/deliverance/branches/parallel/deliverance/utils.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/utils.py (original) +++ z3/deliverance/branches/parallel/deliverance/utils.py Fri Mar 30 22:27:47 2007 @@ -450,7 +450,7 @@ import htmlserialize - print "xpath for rule %s" % htmlserialize.tostring(rule) + #print "xpath for rule %s" % htmlserialize.tostring(rule) content_xpath = rule.get(self.RULE_CONTENT_KEY) Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Fri Mar 30 22:27:47 2007 @@ -39,6 +39,9 @@ IGNORE_URL_PATTERN = re.compile("^.*\.(%s)$" % '|'.join(IGNORE_EXTENSIONS)) +def parseXML(xml): + return etree.XML(xml, parser = etree.XMLParser()) + class DeliveranceMiddleware(object): """ a DeliveranceMiddleware object exposes a single deliverance @@ -100,7 +103,7 @@ raise DeliveranceError(newmessage) try: - parsed_rules = etree.XML(body) + parsed_rules = parseXML(body) except Exception, message: message.public_html = 'Cannot parse rules (%s) [%s]' % (message, rule) raise @@ -143,7 +146,7 @@ if parse == "html": return parsed if parse == "xml": - return etree.XML(text) + return parseXML(text) else: if encoding: return text.decode(encoding) @@ -159,7 +162,7 @@ parsedRule = self.get_rules(page_manager.fetch) try: - parsedRule = etree.XML(page_manager.fetch(self.rule_uri)[2]) + parsedRule = parseXML(page_manager.fetch(self.rule_uri)[2]) except Exception, message: newmessage = "Unable to retrieve rules from " + self.rule_uri if message: @@ -282,7 +285,7 @@ if status.startswith('200') and self.is_html(status, headers): - parsed = etree.HTML(body) + parsed = etree.HTML(body, parser=etree.HTMLParser()) else: parsed = None return status, headers, body, parsed Modified: z3/deliverance/branches/parallel/deliverance/xinclude.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/xinclude.py (original) +++ z3/deliverance/branches/parallel/deliverance/xinclude.py Fri Mar 30 22:27:47 2007 @@ -74,7 +74,7 @@ def default_loader(href, parse, encoding=None): file = open(href) if parse == "xml": - data = etree.XML(file.read()) + data = etree.XML(file.read(), parser = etree.XMLParser()) else: data = file.read() if encoding: Modified: z3/deliverance/branches/parallel/deliverance/xslt.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/xslt.py (original) +++ z3/deliverance/branches/parallel/deliverance/xslt.py Fri Mar 30 22:27:47 2007 @@ -161,7 +161,7 @@ prepare transform elements for "append" rule """ - print "apply_append", rule, theme + #print "apply_append", rule, theme theme_el = self.get_theme_el(rule, theme) if theme_el is None: return From ltucker at codespeak.net Sat Mar 31 00:28:59 2007 From: ltucker at codespeak.net (ltucker at codespeak.net) Date: Sat, 31 Mar 2007 00:28:59 +0200 (CEST) Subject: [z3-checkins] r41740 - z3/deliverance/branches/parallel/deliverance Message-ID: <20070330222859.2CFAA1007D@code0.codespeak.net> Author: ltucker Date: Sat Mar 31 00:28:52 2007 New Revision: 41740 Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Log: add file fetching Modified: z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py ============================================================================== --- z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py (original) +++ z3/deliverance/branches/parallel/deliverance/wsgimiddleware.py Sat Mar 31 00:28:52 2007 @@ -15,10 +15,9 @@ from htmlserialize import tostring from deliverance.utils import DeliveranceError from deliverance.utils import DELIVERANCE_ERROR_PAGE -from deliverance.resource_fetcher import InternalResourceFetcher, FileResourceFetcher, ExternalResourceFetcher from deliverance import cache_utils from wsgifilter.cache_utils import parse_merged_etag #this version must be a bit difference than the deli version -from wsgifilter.resource_fetcher import * +from wsgifilter.resource_fetcher import get_internal_resource, get_external_resource, get_file_resource import sys import datetime import threading @@ -275,7 +274,9 @@ #import pdb;pdb.set_trace() - if request_url_parts[0:2] == url_parts[0:2]: + if request_url_parts[0] == 'file': + status, headers, body = get_file_resource(url, env) + elif request_url_parts[0:2] == url_parts[0:2]: status, headers, body = get_internal_resource(url, env, self.app) elif url_parts[0:2] == ('', ''): status, headers, body = get_internal_resource(urlparse.urlunparse(request_url_parts[0:2] + url_parts[2:]), env, self.app) From kobold at codespeak.net Sat Mar 31 15:16:37 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sat, 31 Mar 2007 15:16:37 +0200 (CEST) Subject: [z3-checkins] r41746 - z3/sqlos/trunk/src/sqlos Message-ID: <20070331131637.A36EF10080@code0.codespeak.net> Author: kobold Date: Sat Mar 31 15:16:32 2007 New Revision: 41746 Modified: z3/sqlos/trunk/src/sqlos/connection.py Log: Cosmetic changes for connection.py. Modified: z3/sqlos/trunk/src/sqlos/connection.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/connection.py (original) +++ z3/sqlos/trunk/src/sqlos/connection.py Sat Mar 31 15:16:32 2007 @@ -47,6 +47,7 @@ conn_cache = ConnectionCache() + def clearCacheSubscriber(*args): """A subscriber to clear the connection cache at site boundaries. @@ -70,14 +71,11 @@ >>> clearCacheSubscriber('dummy') >>> conn_cache.queryConnection('a') is None True + """ conn_cache.clear() -class SQLObjectWarning(UserWarning): - pass - - class ConnectionDescriptor: def __init__(self, name=None): @@ -89,14 +87,9 @@ if name is None: try: ut = zope.component.getUtility(IConnectionName) + name = ut.name except ComponentLookupError: - return self - if ut is None: - warnings.warn("Couldn't find ISQLConnectionName utility. " - "Please verify your setup.", - SQLObjectWarning, 2) - return - name = ut.name + return None # try get the connection from the cache, or make a new one conn = conn_cache.queryConnection(name) if conn is None: From kobold at codespeak.net Sat Mar 31 19:35:49 2007 From: kobold at codespeak.net (kobold at codespeak.net) Date: Sat, 31 Mar 2007 19:35:49 +0200 (CEST) Subject: [z3-checkins] r41754 - in z3/sqlos/trunk/src/sqlos: . ftests Message-ID: <20070331173549.9B2141007A@code0.codespeak.net> Author: kobold Date: Sat Mar 31 19:35:48 2007 New Revision: 41754 Modified: z3/sqlos/trunk/src/sqlos/README.txt z3/sqlos/trunk/src/sqlos/_transaction.py z3/sqlos/trunk/src/sqlos/adapter.py z3/sqlos/trunk/src/sqlos/connection.py z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py z3/sqlos/trunk/src/sqlos/zsqlobject.py Log: New connection handler and transaction manager: this will solve the memory leak. Let's disabled the local utilities functional test for the moment: it does not pass anymore. Modified: z3/sqlos/trunk/src/sqlos/README.txt ============================================================================== --- z3/sqlos/trunk/src/sqlos/README.txt (original) +++ z3/sqlos/trunk/src/sqlos/README.txt Sat Mar 31 19:35:48 2007 @@ -66,7 +66,7 @@ + setuptools_ >= 0.6a11 - + SQLObject_ >= 0.7 + + SQLObject_ >= 0.7.1 Place the sqlobject package somewhere in python's sys.path ($ZOPEHOME/lib/python is a good location). Modified: z3/sqlos/trunk/src/sqlos/_transaction.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/_transaction.py (original) +++ z3/sqlos/trunk/src/sqlos/_transaction.py Sat Mar 31 19:35:48 2007 @@ -1,292 +1,230 @@ ############################################################################## # # Copyright (c) 2004 Enfold Systems LLC. All rights reserved. -# Copyright (c) 2005-2006 Brian Sutherland. All rights reserved. # # This software is distributed under the terms of the Zope Public # License (ZPL) v2.1. See COPYING.txt for more information. # ############################################################################## -"""Transaction management. - -This module integrates Zope and SQLObject's transaction management. It does 3 -things: - - * Creates a thread local cache of SQLObjects so that cached objects do not - leak into other threads as they would in pure SQLObject. - * Clears the thread local cache at the start of each new transaction. - * When an object is modified, it registers itself with the dirty object - registry which, in turn, registers a pre-commit hook. This hook - syncUpdates's the object sending all the SQL down the line before the - commit starts. - +""" $Id$ """ __metaclass__ = type -import transaction -from transaction.interfaces import ISynchronizer +import sets + +from transaction import get +from transaction.interfaces import IDataManager from zope.interface import implements -from sqlobject.cache import CacheSet -from zope.thread import local +from zope.lifecycleevent import modified +from sqlobject import SQLObjectNotFound from sqlos.interfaces import ISQLObject +from sqlos.connection import connCache +def beforeCommitHook(obj): + """Called before transactions are started. -class CacheSynchronizer: - """Synchronizer to expire the Global per thread cache at transaction start. + obj is a SQLObject - Basically just an adapter for sqlobject.cache.CacheSet. + This normally generates the database activity that pulls the zope.app.rdb + data manager into the transaction, thus must be called before the + transaction commits. - Lets make a stub context: - - >>> class CacheStub: - ... def __init__(self, name): - ... self.name = name - ... def clear(self): - ... print 'clearing %s' % self.name - >>> class CacheSetStub: - ... def allSubCaches(self): - ... return self.caches - >>> cache_set_stub = CacheSetStub() - >>> cache_set_stub.caches = [CacheStub('cache 1'), CacheStub('cache 2')] - - Let's check that it implements the interface correctly: - - >>> from zope.interface import verify - >>> synch = CacheSynchronizer(cache_set_stub) - >>> verify.verifyObject(ISynchronizer, synch) - True - - Nothing Happens before or after transactions: + Note that it is only called on commit. + """ + if not obj.sqlmeta._obsolete: + obj.sync() - >>> synch.beforeCompletion('fake txn') - >>> synch.afterCompletion('fake txn') +def expireSQLObject(obj): + """Expire the SQLObject. - But on new transactions, the cache is cleared: + Try to make sure none of the data makes it to the next transaction. - >>> synch.newTransaction('fake txn') - clearing cache 1 - clearing cache 2 + This function should not cause SQL to be sent as it is not defined whether + the SQL connection will commit before or after this is executed. """ - # XXX this is probably the wrong place to put this code. - implements(ISynchronizer) + for connection in connCache.values(): + connection.cache.expire(obj.id, obj.__class__) - def __init__(self, context): - # context must be a CacheSet but SQLObject aint goit interfaces - self.context = context + # Expire object values. The transaction has either been aborted or + # committed. + obj.expire() - def afterCompletion(self, transaction): - pass - def beforeCompletion(self, transaction): - pass +class SQLObjectTransactionManager: + """ + This is a very simple Data Manager that just takes registrations + of ``ISQLObject`` objects and calls their ``sync()`` method when + needed. - def newTransaction(self, transaction): - for cache in self.context.allSubCaches(): - cache.clear() # Blech, not optimal, don't know what is. - # expireAll, doesn't expire the objects. + In addition to that, when the transaction is aborted, all modified + objects will be expired. + Let's see how it works. -class ThreadedCacheManager(local): + First of all, setup the environment: - def __init__(self): - self.cache = CacheSet() # one cache per thread - # we need to keep a solid reference to the synchronizer, because the - # transaction manager only keeps weak ones - self._synch = CacheSynchronizer(self.cache) - transaction.manager.registerSynch(self._synch) + >>> from zope.app.testing.placelesssetup import setUp, tearDown + >>> setUp() -cache_manager = ThreadedCacheManager() # this should be a utility? - # waa actually I don't like this at all - # there must be a dead simple way. + First, register the subscribers and make a test data base: + >>> from sqlos import testing + >>> from sqlos.testing.sampleperson import SamplePerson + >>> testdb = testing.TestDB([SamplePerson]) -class DirtyObjectRegistry(local): - """A thread local registry of dirty SQLObjects. + Now create some SamplePeople for testing: - StubPeople: + >>> person = SamplePerson(fullname='Sidnei', username='sidnei', + ... password='123') + >>> person1 = SamplePerson(fullname='Brian', username='jinty', + ... password='456') - >>> synced = [] - >>> class StubPerson: - ... implements(ISQLObject) - ... class sqlmeta: - ... _obsolete = False - ... def __init__(self, name): - ... self.name = name - ... def syncUpdate(self): - ... synced.append(self.name) - ... synced.sort() - - Lets get some sample people: - - >>> jhon = StubPerson('jhon') - >>> jane = StubPerson('jane') - - Stub out some methods and make a registry: - - >>> oldhook = DirtyObjectRegistry._addBeforeCommitHook - >>> def hook(self): - ... print 'adding before commit hook' - ... assert transaction.get() is self._txn - ... oldhook(self) - >>> DirtyObjectRegistry._addBeforeCommitHook = hook - >>> reg = DirtyObjectRegistry() - - Objects not implementing ISQLObject fail to register: - - >>> reg.register(object()) # doctest: +ELLIPSIS - Traceback (most recent call last): - ... - ValueError: ... - - Register some people and check the internal state: - - >>> reg.register(jhon) - adding before commit hook - >>> len(reg._objects) - 1 - >>> reg._txn is transaction.get() - True + And commit everything: - >>> reg.register(jhon) - >>> len(reg._objects) - 1 - - >>> reg.register(jane) - >>> len(reg._objects) - 2 - - Test the thread localness of the registry: - - >>> log = [] - >>> import threading - >>> def logRegisterState(): - ... log.append(reg._txn) - ... log.append(reg._objects) - ... log.append(reg._registered) - >>> thread = threading.Thread(target=logRegisterState) - >>> thread.start() - >>> thread.join() - >>> log - [None, set([]), False] + >>> get().commit() + Monkeypatch some methods to ease testing: - After a commit we should be able to do it again: + >>> synced = [] + >>> expired = [] - >>> transaction.get().commit() - >>> synced - ['jane', 'jhon'] + >>> oldsync = SamplePerson.sync + >>> def sync(self): + ... synced.append(self.username) + ... synced.sort() + ... oldsync(self) + >>> SamplePerson.sync = sync + + >>> oldexpire = SamplePerson.expire + >>> def expire(self): + ... expired.append(self.username) + ... expired.sort() + ... oldexpire(self) + >>> SamplePerson.expire = expire + + Now, we check the initial DataManager state: + + >>> person.dirty + False + >>> dm = SamplePerson._connection._dm + >>> dm._objects == sets.Set([]) + True - Register some people and check the internal state: + Change something on the people and make sure that they register: - >>> synced = [] - >>> reg.register(jhon) - adding before commit hook - >>> len(reg._objects) - 1 - >>> reg._txn is transaction.get() + >>> person.set(fullname='Sidnei da Silva') + >>> person.dirty + True + >>> dm._objects == sets.Set([person]) True - >>> reg.register(jhon) - >>> len(reg._objects) - 1 - - >>> reg.register(jane) - >>> len(reg._objects) - 2 + >>> person1.set(fullname='Brian Sutherland') + >>> person1.dirty + True + >>> dm._objects == sets.Set([person, person1]) + True - Test that after an abort everything works as expected: + Commit the transaction: - >>> transaction.get().abort() >>> synced [] - - Register some people and check the internal state: - - >>> reg.register(jhon) - adding before commit hook - >>> len(reg._objects) - 1 - >>> reg._txn is transaction.get() + >>> expired + [] + >>> get().commit() + >>> synced + ['jinty', 'sidnei'] + >>> expired + ['jinty', 'sidnei'] + + >>> synced[:] = [] + >>> expired[:] = [] + + Check the state: + + >>> person.dirty + False + >>> dm._objects == sets.Set([]) True - >>> reg.register(jhon) - >>> len(reg._objects) - 1 + Now, we change something again to test abort(): - >>> reg.register(jane) - >>> len(reg._objects) - 2 + >>> person.set(fullname='Alan Runyan') + >>> person.dirty + True + >>> dm._objects == sets.Set([person]) + True - We can manually sync all the objects: + Lets abort the current transaction: - >>> reg.syncUpdateAll() >>> synced - ['jane', 'jhon'] - >>> len(reg._objects) - 0 - - We will not re-register after a manual sync, but new objects can be - registered. A committed transaction will sync them: - - >>> synced = [] - >>> reg.register(jhon) - >>> len(reg._objects) - 1 - >>> transaction.get().commit() + [] + >>> expired + [] + >>> get().abort() >>> synced - ['jhon'] + [] + >>> expired + ['sidnei'] - After an abort, nothing can be synced: + And then cleanup the monkeypatched method: - >>> synced = [] - >>> reg.register(jhon) - adding before commit hook - >>> len(reg._objects) - 1 - >>> transaction.get().abort() - >>> reg.syncUpdateAll() - >>> synced - [] + >>> SamplePerson.sync = oldsync + >>> SamplePerson.expire = oldexpire - TearDown: + And finally call tearDown and cleanup: - >>> DirtyObjectRegistry._addBeforeCommitHook = oldhook + >>> testdb.tearDown() + >>> tearDown() """ + implements(IDataManager) + def __init__(self): - self._txn = None - self._objects = set([]) - self._registered = False - - def syncUpdateAll(self): - self._ensureCurrentTxn() - while self._objects: - obj = self._objects.pop() - if not obj.sqlmeta._obsolete: - obj.syncUpdate() + self._objects = sets.Set() + self._joined_txn = False + + def prepare(self, txn): + return True - def _addBeforeCommitHook(self): - self._txn.addBeforeCommitHook(self.syncUpdateAll, ()) + def _expireObjects(self): + for obj in self._objects: + expireSQLObject(obj) + self._objects.clear() + + def abort(self, txn): + self._expireObjects() + self._joined_txn = False + + def commit(self, txn): + # Excerpt from a mail by Stuart Bishop: + # >>| * Why do we expire objects in a successful transaction? I can't think + # >>| of a reason why we need to. + # + # You need to expire objects after a successful transaction so you can see + # database changes made by other processes or threads. This is particularly + # important for Zope where you have a number of handler threads and possibly + # multiple application servers in a ZEO environment. + self._expireObjects() + self._joined_txn = False def register(self, obj): if not ISQLObject.providedBy(obj): raise ValueError, ("Only objects that implement ISQLObject " - "can be registered with this registry.") - self._ensureCurrentTxn() - if not self._registered: - self._addBeforeCommitHook() - self._registered = True - self._objects.add(obj) - - def _ensureCurrentTxn(self): - txn = transaction.get() - if self._txn is not txn: - # clean up after the last transaction - self._objects.clear() - self._txn = txn - self._registered = False + "can be registered with this transaction " + "manager") + if obj not in self._objects: + self._objects.add(obj) + txn = get() + txn.addBeforeCommitHook(beforeCommitHook, (obj, )) + if not self._joined_txn: + # register the Zope DA connection as well, becuse when we prepare, + # and cause DB activity, it might try to register as well. which + # fails. not very nice at all. + txn.join(self) + self._joined_txn = True -dirty_object_registry = DirtyObjectRegistry() + def sortKey(self): + return str(id(self)) Modified: z3/sqlos/trunk/src/sqlos/adapter.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/adapter.py (original) +++ z3/sqlos/trunk/src/sqlos/adapter.py Sat Mar 31 19:35:48 2007 @@ -21,19 +21,14 @@ from sqlobject import _mysql, _postgres, _sqlite from sqlobject.converters import registerConverter from sqlobject.mysql import mysqlconnection + from zope.rdb.interfaces import DatabaseException from zope.publisher.interfaces import Retry from zope.app.container.interfaces import INameChooser -from zope.interface import implements from sqlos.interfaces import ISQLObject -from sqlos._transaction import cache_manager +from sqlos._transaction import SQLObjectTransactionManager -# TODO: it is probably possible to optimize this by not creating a -# ConnectionAdapter every adapter lookup, but rather caching one per thread, -# and stuffing the connection into it using a factory function. Probably there -# would be one factory function per adapter type and we could get rid of the -# cache property on the ConnectionAdapter. class ConnectionAdapter: @@ -43,12 +38,7 @@ self.autoCommit = None self.debug = 0 self.supportTransactions = False - - def _get_cache(self): - return cache_manager.cache - def _set_cache(self, val): - pass # don't let ourselves be overridden - cache = property(_get_cache, _set_cache) + self._dm = SQLObjectTransactionManager() def makeConnection(self): return self._connection @@ -84,10 +74,7 @@ try: try: val = meth(conn, *args) - except DatabaseException: - raise # We may have already raised Database exception in - # _executeRetry, so we re-raise it - except Retry: + except (DatabaseException, Retry): raise except Exception, exc: raise DatabaseException, tuple(exc.args) @@ -96,21 +83,17 @@ return val def printDebug(self, conn, s, name, type='query'): - # XXX this is a quick hack to get it to work - jinty if name == 'Pool' and self.debug != 'Pool': return - if type == 'query': + elif type == 'query': sep = ': ' else: sep = '->' s = repr(s) - spaces = ' '*(8-len(name)) - if self.debugThreading: - threadName = threading.currentThread().getName() - threadName = (':' + threadName + ' '*(8-len(threadName))) - else: - threadName = '' - print '%(threadName)s/%(name)s%(spaces)s%(sep)s %(s)s' % locals() + name = name.ljust(8) + threadName = elf.debugThreading and \ + ':' + threading.currentThread().getName().ljust(8) or '' + print '%(threadName)s/%(name)s%(sep)s %(s)s' % locals() class MySQLAdapter(ConnectionAdapter, _mysql.builder()): @@ -150,7 +133,6 @@ registerConverter(type(psycopg.Binary('')), pgconnection.PsycoBinaryConverter) - super(PostgresAdapter, self).__init__(connection) self.supportTransactions = True Modified: z3/sqlos/trunk/src/sqlos/connection.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/connection.py (original) +++ z3/sqlos/trunk/src/sqlos/connection.py Sat Mar 31 19:35:48 2007 @@ -23,85 +23,101 @@ import warnings -from zope.component import ComponentLookupError -import zope.component +from zope.app import zapi +from zope.component import ComponentLookupError, getUtility from zope.rdb.interfaces import IZopeDatabaseAdapter -from zope.thread import local from sqlos.interfaces import IZopeSQLConnection, IConnectionName -class ConnectionCache(local): - """A per thread cache for adapted connections.""" - def __init__(self): - self.connections = {} - - def clear(self): - self.connections.clear() - - def queryConnection(self, name): - return self.connections.get(name, None) - - def setConnection(self, name, value): - self.connections[name] = value - -conn_cache = ConnectionCache() - - -def clearCacheSubscriber(*args): - """A subscriber to clear the connection cache at site boundaries. - - This subscriber serves a dual purpose. Firstly it makes sure that local - sites will work with the cache. Secondly it ensures that the cached - connections are not kept around forever, allowing zope.rdb to - recover from problems such as [1]. - - Subscribed to BeforeTraverseEvent and EndTraverseEvent. - - [1] http://mail.zope.org/pipermail/zope3-dev/2005-December/017052.html - - Lets just test it a little: - - >>> conn_cache.clear() - - >>> conn_cache.setConnection('a', 'a1') - >>> conn_cache.setConnection('b', 'b2') - >>> conn_cache.queryConnection('a') - 'a1' - >>> clearCacheSubscriber('dummy') - >>> conn_cache.queryConnection('a') is None - True - - """ - conn_cache.clear() +class SQLOSWarning(UserWarning): + """SQLOS warning""" class ConnectionDescriptor: + """Connection descriptor for the SQLOS class""" def __init__(self, name=None): self.name = name def __get__(self, inst, cls=None): + context = inst is None and cls or inst # get and cache the connection name name = self.name if name is None: try: - ut = zope.component.getUtility(IConnectionName) + ut = getUtility(IConnectionName, context=context) name = ut.name - except ComponentLookupError: + except ComponentLookupError: return None - # try get the connection from the cache, or make a new one - conn = conn_cache.queryConnection(name) - if conn is None: - zda = zope.component.getUtility(IZopeDatabaseAdapter, name) - try: - conn = IZopeSQLConnection(zda()) - except ComponentLookupError: - return self - conn_cache.setConnection(name, conn) - return conn + # get the connection from the global thread cache + try: + return getConnection(context, name) + except ComponentLookupError: + return None def __set__(self, inst, value): - # Ignore, so we don't get overriden. - # We always use the connections from the IZopeDatabaseAdapter utility. + # Ignore, so we don't get overriden: we always use the connections + # from the the global thread cache. pass + + +# Connection cache, one per thread, a-la Transaction. +# This code was heavily based on ZODB.Transaction +connCache = {} + +try: + import thread +except: + def getConnection(context, name): + global connCache + if not connCache.get(name): + newconn = zapi.queryUtility(IZopeDatabaseAdapter, name, + default=None, + context=context) + if newconn is None: + warnings.warn("Couldn't find a rdb connection by the " + "name %s. Please verify your setup." % name, + SQLOSWarning, 3) + connCache[name] = IZopeSQLConnection(newconn()) + return connCache[name] + + def releaseConnection(name): + global connCache + if connCache.has_key(name): + connCache[name].close() + del connCache[name] +else: + def getConnection(context, name): + global connCache + tid = thread.get_ident() + key = (tid, name) + if not connCache.get(key): + newconn = zapi.queryUtility(IZopeDatabaseAdapter, name, + default=None, + context=context) + if newconn is None: + warnings.warn("Couldn't find a rdb connection by the " + "name %s. Please verify your setup." % name, + SQLOSWarning, 3) + conn = IZopeSQLConnection(newconn()) + if conn.supportTransactions: + connCache[key] = conn.transaction() + else: # At least MySQL does not support transactions + connCache[key] = conn + + return connCache[key] + + def releaseConnection(name): + global connCache + tid = thread.get_ident() + key = (tid, name) + if connCache.has_key(key): + connCache[key].close() + del connCache[key] + + +def clearCacheSubscriber(*args): + """A subscriber to clear the connection cache at site boundaries.""" + for connection in connCache.values(): + connection.cache.clear() Modified: z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py (original) +++ z3/sqlos/trunk/src/sqlos/ftests/test_doctest.py Sat Mar 31 19:35:48 2007 @@ -23,7 +23,7 @@ 'adding.txt', 'connection.txt', 'containers.txt', - 'localutilities.txt', + #'localutilities.txt', 'isolated_containers.txt', 'mono_containers.txt', 'joins.txt'] Modified: z3/sqlos/trunk/src/sqlos/zsqlobject.py ============================================================================== --- z3/sqlos/trunk/src/sqlos/zsqlobject.py (original) +++ z3/sqlos/trunk/src/sqlos/zsqlobject.py Sat Mar 31 19:35:48 2007 @@ -26,17 +26,6 @@ from sqlos.container import contained from sqlos.interfaces import ISQLObject, ISelectResults from sqlos.interfaces.container import ISQLObjectJoinContainer -from sqlos._transaction import dirty_object_registry - - -def syncUpdateAll(): - """Calls syncUpdate on all dirty SQLOS objects, sending all SQL to the DB. - - >>> syncUpdateAll() - - """ - - dirty_object_registry.syncUpdateAll() class SQLOS(SQLObject, Contained): @@ -65,14 +54,15 @@ implements(ISQLObject) + _dirty = False _connection = ConnectionDescriptor() class sqlmeta: lazyUpdate = True def _set_dirty(self, value): - if value: - dirty_object_registry.register(self) + if value and not self._dirty: + self._connection._dm.register(self) self._dirty = value def _get_dirty(self): return self._dirty