Merge lp:~sidnei/zope3/ztk-1.0a1 into lp:~landscape/zope3/trunk

Proposed by Sidnei da Silva
Status: Merged
Approved by: Muharem Hrnjadovic
Approved revision: 16
Merged at revision: 16
Proposed branch: lp:~sidnei/zope3/ztk-1.0a1
Merge into: lp:~landscape/zope3/trunk
Diff against target: 41688 lines (+18120/-9441)
556 files modified
src/ClientForm.py (+0/-3401)
src/ZConfig/__init__.py (+1/-1)
src/ZConfig/cfgparser.py (+1/-1)
src/ZConfig/cmdline.py (+1/-1)
src/ZConfig/components/basic/mapping.py (+1/-1)
src/ZConfig/components/basic/tests/test_mapping.py (+1/-1)
src/ZConfig/components/logger/__init__.py (+1/-1)
src/ZConfig/components/logger/datatypes.py (+1/-1)
src/ZConfig/components/logger/factory.py (+1/-1)
src/ZConfig/components/logger/handlers.py (+15/-2)
src/ZConfig/components/logger/handlers.xml (+2/-0)
src/ZConfig/components/logger/logger.py (+1/-1)
src/ZConfig/components/logger/loghandler.py (+1/-1)
src/ZConfig/components/logger/tests/test_logger.py (+49/-1)
src/ZConfig/datatypes.py (+1/-1)
src/ZConfig/info.py (+1/-1)
src/ZConfig/loader.py (+11/-3)
src/ZConfig/matcher.py (+1/-1)
src/ZConfig/schema.py (+1/-1)
src/ZConfig/schemaless.py (+1/-1)
src/ZConfig/substitution.py (+1/-1)
src/ZConfig/tests/__init__.py (+1/-1)
src/ZConfig/tests/library/thing/__init__.py (+1/-1)
src/ZConfig/tests/support.py (+1/-1)
src/ZConfig/tests/test_cfgimports.py (+1/-1)
src/ZConfig/tests/test_cmdline.py (+1/-1)
src/ZConfig/tests/test_config.py (+1/-1)
src/ZConfig/tests/test_cookbook.py (+1/-1)
src/ZConfig/tests/test_datatypes.py (+1/-1)
src/ZConfig/tests/test_loader.py (+6/-1)
src/ZConfig/tests/test_readme.py (+1/-1)
src/ZConfig/tests/test_schema.py (+1/-1)
src/ZConfig/tests/test_schemaless.py (+1/-1)
src/ZConfig/tests/test_subst.py (+1/-1)
src/ZConfig/url.py (+1/-1)
src/ZEO/cache.py (+31/-20)
src/ZEO/mkzeoinst.py (+9/-227)
src/ZEO/tests/testZEO.py (+4/-1)
src/ZODB/ConflictResolution.py (+5/-1)
src/ZODB/ConflictResolution.txt (+11/-1)
src/ZODB/Connection.py (+3/-2)
src/ZODB/scripts/tests/test_repozo.py (+4/-10)
src/ZODB/serialize.py (+27/-10)
src/ZODB/tests/testConnection.py (+20/-4)
src/ZODB/tests/testConnectionSavepoint.py (+29/-1)
src/Zope3.egg-info/PKG-INFO (+10/-0)
src/Zope3.egg-info/SOURCES.txt (+1924/-0)
src/Zope3.egg-info/dependency_links.txt (+1/-0)
src/Zope3.egg-info/namespace_packages.txt (+3/-0)
src/Zope3.egg-info/not-zip-safe (+1/-0)
src/Zope3.egg-info/top_level.txt (+14/-0)
src/mechanize/__init__.py (+83/-15)
src/mechanize/_auth.py (+4/-458)
src/mechanize/_beautifulsoup.py (+2/-5)
src/mechanize/_clientcookie.py (+14/-21)
src/mechanize/_debug.py (+1/-1)
src/mechanize/_file.py (+0/-60)
src/mechanize/_firefox3cookiejar.py (+4/-5)
src/mechanize/_form.py (+3275/-0)
src/mechanize/_gzip.py (+4/-2)
src/mechanize/_html.py (+43/-53)
src/mechanize/_http.py (+157/-468)
src/mechanize/_markupbase.py (+393/-0)
src/mechanize/_mechanize.py (+14/-20)
src/mechanize/_msiecookiejar.py (+1/-1)
src/mechanize/_opener.py (+73/-67)
src/mechanize/_pullparser.py (+4/-3)
src/mechanize/_request.py (+4/-51)
src/mechanize/_response.py (+6/-8)
src/mechanize/_rfc3986.py (+4/-0)
src/mechanize/_seek.py (+0/-16)
src/mechanize/_sgmllib_copy.py (+559/-0)
src/mechanize/_testcase.py (+89/-1)
src/mechanize/_upgrade.py (+0/-40)
src/mechanize/_urllib2.py (+21/-26)
src/mechanize/_urllib2_fork.py (+1410/-0)
src/mechanize/_useragent.py (+26/-11)
src/mechanize/_util.py (+16/-2)
src/mechanize/_version.py (+2/-0)
src/persistent/cPickleCache.c (+6/-4)
src/persistent/wref.py (+59/-2)
src/transaction/__init__.py (+5/-3)
src/transaction/_manager.py (+46/-59)
src/transaction/_transaction.py (+10/-2)
src/transaction/interfaces.py (+21/-13)
src/transaction/tests/convenience.txt (+183/-0)
src/transaction/tests/sampledm.py (+2/-2)
src/transaction/tests/savepointsample.py (+2/-3)
src/transaction/tests/test_SampleDataManager.py (+2/-4)
src/transaction/tests/test_SampleResourceManager.py (+2/-2)
src/transaction/tests/test_register_compat.py (+2/-3)
src/transaction/tests/test_savepoint.py (+24/-5)
src/transaction/tests/test_transaction.py (+68/-10)
src/transaction/tests/test_weakset.py (+1/-1)
src/transaction/tests/warnhook.py (+1/-1)
src/transaction/weakset.py (+1/-1)
src/wsgi_intercept/__init__.py (+565/-0)
src/wsgi_intercept/httplib2_intercept.py (+50/-0)
src/wsgi_intercept/mechanize_intercept/__init__.py (+6/-0)
src/wsgi_intercept/mechanize_intercept/wsgi_browser.py (+33/-0)
src/wsgi_intercept/mechanoid_intercept/__init__.py (+6/-0)
src/wsgi_intercept/mechanoid_intercept/wsgi_browser.py (+35/-0)
src/wsgi_intercept/mock_http.py (+70/-0)
src/wsgi_intercept/setup_cmd/__init__.py (+1/-0)
src/wsgi_intercept/setup_cmd/build_docs.py (+146/-0)
src/wsgi_intercept/setup_cmd/publish_docs.py (+199/-0)
src/wsgi_intercept/test/test_httplib2.py (+38/-0)
src/wsgi_intercept/test/test_mechanize.py (+47/-0)
src/wsgi_intercept/test/test_mechanoid.py (+34/-0)
src/wsgi_intercept/test/test_webtest.py (+37/-0)
src/wsgi_intercept/test/test_webunit.py (+47/-0)
src/wsgi_intercept/test/test_wsgi_compliance.py (+55/-0)
src/wsgi_intercept/test/test_wsgi_urllib2.py (+40/-0)
src/wsgi_intercept/test/test_zope_testbrowser.py (+41/-0)
src/wsgi_intercept/test_wsgi_app.py (+24/-0)
src/wsgi_intercept/urllib2_intercept/__init__.py (+7/-0)
src/wsgi_intercept/urllib2_intercept/wsgi_urllib2.py (+59/-0)
src/wsgi_intercept/webtest_intercept/__init__.py (+7/-0)
src/wsgi_intercept/webtest_intercept/webtest.py (+418/-0)
src/wsgi_intercept/webunit_intercept/HTMLParser.py (+439/-0)
src/wsgi_intercept/webunit_intercept/IMGSucker.py (+112/-0)
src/wsgi_intercept/webunit_intercept/SimpleDOM.py (+473/-0)
src/wsgi_intercept/webunit_intercept/__init__.py (+8/-0)
src/wsgi_intercept/webunit_intercept/config.py (+33/-0)
src/wsgi_intercept/webunit_intercept/cookie.py (+92/-0)
src/wsgi_intercept/webunit_intercept/utility.py (+85/-0)
src/wsgi_intercept/webunit_intercept/webunittest.py (+743/-0)
src/wsgi_intercept/zope_testbrowser/__init__.py (+6/-0)
src/wsgi_intercept/zope_testbrowser/wsgi_testbrowser.py (+20/-0)
src/zope/app/appsetup/appsetup.py (+7/-4)
src/zope/app/appsetup/bootstrap.txt (+1/-1)
src/zope/app/appsetup/ftesting.zcml (+20/-0)
src/zope/app/appsetup/product.py (+7/-2)
src/zope/app/appsetup/testlayer.py (+82/-0)
src/zope/app/appsetup/testlayer.txt (+62/-0)
src/zope/app/appsetup/testpackage/__init__.py (+1/-0)
src/zope/app/appsetup/testpackage/ftesting.zcml (+6/-0)
src/zope/app/appsetup/testpackage/testobject.py (+5/-0)
src/zope/app/appsetup/tests.py (+50/-44)
src/zope/app/dav/proppatch.py (+29/-14)
src/zope/app/dav/tests/test_proppatch.py (+0/-5)
src/zope/app/http/exception/methodnotallowed.py (+15/-8)
src/zope/app/http/exception/tests/test_methodnotallowed.py (+47/-10)
src/zope/app/http/testing.py (+0/-26)
src/zope/app/http/tests/test_delete.py (+2/-3)
src/zope/app/http/tests/test_functional_put.py (+22/-19)
src/zope/app/http/tests/test_options.py (+2/-3)
src/zope/app/http/tests/test_put.py (+16/-6)
src/zope/app/locales/de/LC_MESSAGES/zope.po (+389/-980)
src/zope/app/locales/nl/LC_MESSAGES/zope.po (+4/-8)
src/zope/app/locales/tests.py (+29/-1)
src/zope/app/localpermission/browser.zcml (+1/-0)
src/zope/app/pagetemplate/meta.zcml (+1/-8)
src/zope/app/pagetemplate/metaconfigure.py (+4/-31)
src/zope/app/pagetemplate/tests/test_directives.py (+0/-60)
src/zope/app/principalannotation/bootstrap.zcml (+1/-1)
src/zope/app/publication/ftesting.zcml (+41/-29)
src/zope/app/publication/httpfactory.txt (+7/-5)
src/zope/app/publication/methodnotallowed.txt (+7/-6)
src/zope/app/publication/testing.py (+15/-7)
src/zope/app/publication/tests/ftest_zcml_dependencies.zcml (+2/-2)
src/zope/app/publication/tests/support.py (+29/-0)
src/zope/app/publication/tests/test_browserpublication.py (+27/-13)
src/zope/app/publication/tests/test_dependencies.py (+12/-15)
src/zope/app/publication/tests/test_functional.py (+14/-10)
src/zope/app/publication/tests/test_http.py (+4/-5)
src/zope/app/publication/tests/test_httpfactory.py (+1/-2)
src/zope/app/publication/tests/test_simplecomponenttraverser.py (+6/-5)
src/zope/app/publication/tests/test_xmlrpcpublication.py (+16/-11)
src/zope/app/publication/tests/test_zopepublication.py (+124/-87)
src/zope/app/testing/dochttp.py (+2/-2)
src/zope/app/testing/functional.py (+3/-4)
src/zope/app/testing/placelesssetup.py (+2/-2)
src/zope/app/testing/setup.py (+2/-2)
src/zope/app/testing/testing.py (+2/-2)
src/zope/app/testing/tests.py (+3/-3)
src/zope/app/testing/xmlrpc.py (+2/-2)
src/zope/app/testing/ztapi.py (+2/-2)
src/zope/app/wsgi/README.txt (+21/-40)
src/zope/app/wsgi/__init__.py (+15/-6)
src/zope/app/wsgi/ftesting.zcml (+22/-10)
src/zope/app/wsgi/testing.py (+6/-7)
src/zope/app/wsgi/testlayer.py (+273/-0)
src/zope/app/wsgi/tests.py (+27/-19)
src/zope/app/zapi/README.txt (+0/-42)
src/zope/app/zapi/__init__.py (+0/-57)
src/zope/app/zapi/configure.zcml (+0/-23)
src/zope/app/zapi/interfaces.py (+0/-67)
src/zope/app/zapi/tests.py (+0/-59)
src/zope/authentication/tests/test_logout.py (+2/-2)
src/zope/authentication/tests/test_principal.py (+2/-2)
src/zope/browser/__init__.py (+1/-1)
src/zope/browser/interfaces.py (+109/-109)
src/zope/browser/tests.py (+32/-32)
src/zope/browsermenu/tests/test_addMenuItem.py (+2/-2)
src/zope/browsermenu/tests/test_directives.py (+2/-2)
src/zope/browsermenu/tests/test_fields.py (+3/-2)
src/zope/browsermenu/tests/test_menu.py (+5/-3)
src/zope/browserpage/meta.zcml (+9/-0)
src/zope/browserpage/metaconfigure.py (+9/-2)
src/zope/browserpage/metadirectives.py (+19/-2)
src/zope/browserpage/namedtemplate.py (+1/-1)
src/zope/browserpage/simpleviewclass.py (+2/-2)
src/zope/browserpage/tests/sample.py (+2/-2)
src/zope/browserpage/tests/simpletestview.py (+2/-2)
src/zope/browserpage/tests/test_boundpagetemplate.py (+2/-2)
src/zope/browserpage/tests/test_expressiontype.py (+60/-0)
src/zope/browserpage/tests/test_namedtemplate.py (+6/-4)
src/zope/browserpage/tests/test_page.py (+3/-3)
src/zope/browserpage/tests/test_simpleviewclass.py (+2/-2)
src/zope/browserpage/tests/test_viewzpt.py (+2/-2)
src/zope/browserpage/viewpagetemplatefile.py (+2/-2)
src/zope/browserresource/tests/test_file.py (+4/-2)
src/zope/browserresource/tests/test_resources.py (+4/-2)
src/zope/cachedescriptors/property.txt (+18/-18)
src/zope/cachedescriptors/tests.py (+2/-7)
src/zope/component/__init__.py (+2/-2)
src/zope/component/_api.py (+2/-2)
src/zope/component/_declaration.py (+2/-2)
src/zope/component/event.py (+2/-2)
src/zope/component/eventtesting.py (+2/-2)
src/zope/component/factory.py (+2/-2)
src/zope/component/globalregistry.py (+2/-2)
src/zope/component/hookable.py (+1/-1)
src/zope/component/hooks.py (+1/-1)
src/zope/component/interface.py (+2/-2)
src/zope/component/interfaces.py (+2/-2)
src/zope/component/nexttesting.py (+2/-2)
src/zope/component/persistentregistry.py (+2/-2)
src/zope/component/registry.py (+2/-2)
src/zope/component/security.py (+1/-1)
src/zope/component/testfiles/adapter.py (+2/-2)
src/zope/component/testfiles/components.py (+2/-2)
src/zope/component/testfiles/views.py (+2/-2)
src/zope/component/testing.py (+2/-2)
src/zope/component/testlayer.py (+1/-1)
src/zope/component/tests.py (+27/-3)
src/zope/component/zcml.py (+3/-3)
src/zope/component/zcml.txt (+2/-2)
src/zope/component/zcml_conditional.txt (+2/-2)
src/zope/configuration/__init__.py (+1/-1)
src/zope/configuration/config.py (+2/-2)
src/zope/configuration/docutils.py (+2/-2)
src/zope/configuration/exceptions.py (+2/-2)
src/zope/configuration/fields.py (+2/-2)
src/zope/configuration/interfaces.py (+2/-2)
src/zope/configuration/name.py (+2/-2)
src/zope/configuration/stxdocs.py (+2/-2)
src/zope/configuration/tests/directives.py (+2/-2)
src/zope/configuration/tests/samplepackage/foo.py (+2/-2)
src/zope/configuration/tests/test_conditions.py (+3/-3)
src/zope/configuration/tests/test_config.py (+3/-3)
src/zope/configuration/tests/test_docutils.py (+3/-3)
src/zope/configuration/tests/test_nested.py (+3/-3)
src/zope/configuration/tests/test_simple.py (+3/-3)
src/zope/configuration/tests/test_xmlconfig.py (+3/-3)
src/zope/configuration/xmlconfig.py (+2/-2)
src/zope/configuration/zopeconfigure.py (+2/-2)
src/zope/container/contained.py (+53/-43)
src/zope/container/tests/test_btree.py (+2/-2)
src/zope/container/tests/test_constraints.py (+4/-2)
src/zope/container/tests/test_contained.py (+94/-5)
src/zope/container/tests/test_directory.py (+5/-3)
src/zope/container/tests/test_ordered.py (+4/-2)
src/zope/contentprovider/__init__.py (+2/-2)
src/zope/contentprovider/configure.zcml (+1/-1)
src/zope/contentprovider/interfaces.py (+2/-2)
src/zope/contentprovider/tales.py (+2/-2)
src/zope/contentprovider/tests.py (+12/-11)
src/zope/contenttype/__init__.py (+14/-18)
src/zope/contenttype/mime.types (+1/-1)
src/zope/contenttype/tests/testContentTypes.py (+47/-39)
src/zope/deferredimport/DEPENDENCIES.cfg (+0/-2)
src/zope/deferredimport/deferredmodule.py (+2/-2)
src/zope/deferredimport/tests.py (+17/-7)
src/zope/event/__init__.py (+2/-2)
src/zope/event/tests.py (+30/-4)
src/zope/exceptions/__init__.py (+2/-2)
src/zope/exceptions/exceptionformatter.py (+2/-2)
src/zope/exceptions/interfaces.py (+2/-2)
src/zope/exceptions/log.py (+2/-2)
src/zope/exceptions/tests/test_exceptionformatter.py (+3/-4)
src/zope/formlib/__init__.py (+2/-2)
src/zope/formlib/boolwidgets.py (+2/-2)
src/zope/formlib/errors.py (+2/-2)
src/zope/formlib/exception.py (+2/-2)
src/zope/formlib/form.py (+53/-39)
src/zope/formlib/form.txt (+12/-6)
src/zope/formlib/interfaces.py (+2/-2)
src/zope/formlib/itemswidgets.py (+7/-7)
src/zope/formlib/namedtemplate.py (+2/-2)
src/zope/formlib/objectwidget.py (+2/-2)
src/zope/formlib/sequencewidget.py (+2/-2)
src/zope/formlib/source.py (+2/-2)
src/zope/formlib/source.txt (+1/-1)
src/zope/formlib/tests/support.py (+2/-2)
src/zope/formlib/tests/test_browserwidget.py (+2/-2)
src/zope/formlib/tests/test_checkboxwidget.py (+2/-2)
src/zope/formlib/tests/test_choicecollections.py (+2/-2)
src/zope/formlib/tests/test_choicewidget.py (+2/-2)
src/zope/formlib/tests/test_datetimewidget.py (+2/-2)
src/zope/formlib/tests/test_datewidget.py (+2/-2)
src/zope/formlib/tests/test_decimalwidget.py (+2/-2)
src/zope/formlib/tests/test_displaywidget.py (+2/-2)
src/zope/formlib/tests/test_exception.py (+2/-2)
src/zope/formlib/tests/test_filewidget.py (+2/-2)
src/zope/formlib/tests/test_floatwidget.py (+2/-2)
src/zope/formlib/tests/test_formlib.py (+2/-11)
src/zope/formlib/tests/test_functional_booleanradiowidget.py (+2/-2)
src/zope/formlib/tests/test_functional_checkboxwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_datetimewidget.py (+2/-2)
src/zope/formlib/tests/test_functional_decimalwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_filewidget.py (+2/-2)
src/zope/formlib/tests/test_functional_floatwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_intwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_objectwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_selectwidget.py (+2/-2)
src/zope/formlib/tests/test_functional_textareawidget.py (+2/-2)
src/zope/formlib/tests/test_functional_textwidget.py (+2/-2)
src/zope/formlib/tests/test_intwidget.py (+2/-2)
src/zope/formlib/tests/test_itemswidget.py (+52/-6)
src/zope/formlib/tests/test_multicheckboxwidget.py (+2/-2)
src/zope/formlib/tests/test_objectwidget.py (+2/-2)
src/zope/formlib/tests/test_passwordwidget.py (+2/-2)
src/zope/formlib/tests/test_radiowidget.py (+2/-2)
src/zope/formlib/tests/test_registrations.py (+2/-2)
src/zope/formlib/tests/test_selectwidget.py (+2/-2)
src/zope/formlib/tests/test_sequencewidget.py (+2/-2)
src/zope/formlib/tests/test_setprefix.py (+2/-2)
src/zope/formlib/tests/test_source.py (+2/-2)
src/zope/formlib/tests/test_textareawidget.py (+2/-2)
src/zope/formlib/tests/test_textwidget.py (+2/-2)
src/zope/formlib/tests/test_widget.py (+2/-2)
src/zope/formlib/tests/test_widgetdocs.py (+2/-2)
src/zope/formlib/textwidgets.py (+2/-2)
src/zope/formlib/utility.py (+1/-1)
src/zope/formlib/widget.py (+2/-2)
src/zope/formlib/widgets.py (+2/-2)
src/zope/i18n/locales/tests/test_docstrings.py (+4/-3)
src/zope/i18n/locales/tests/test_fallbackcollator.py (+1/-1)
src/zope/i18n/tests/test.py (+2/-2)
src/zope/i18n/tests/test_testmessagecatalog.py (+1/-1)
src/zope/i18nmessageid/__init__.py (+2/-2)
src/zope/i18nmessageid/_zope_i18nmessageid_message.c (+3/-3)
src/zope/i18nmessageid/message.py (+2/-2)
src/zope/i18nmessageid/tests.py (+4/-3)
src/zope/interface/README.ru.txt (+22/-16)
src/zope/interface/README.txt (+38/-18)
src/zope/interface/__init__.py (+4/-3)
src/zope/interface/_flatten.py (+1/-1)
src/zope/interface/_zope_interface_coptimizations.c (+85/-45)
src/zope/interface/_zope_interface_coptimizations.py (+7/-0)
src/zope/interface/adapter.py (+11/-1)
src/zope/interface/adapter.ru.txt (+3/-6)
src/zope/interface/adapter.txt (+3/-6)
src/zope/interface/advice.py (+20/-9)
src/zope/interface/common/idatetime.py (+2/-2)
src/zope/interface/common/interfaces.py (+1/-1)
src/zope/interface/common/mapping.py (+2/-2)
src/zope/interface/common/sequence.py (+2/-2)
src/zope/interface/common/tests/basemapping.py (+2/-8)
src/zope/interface/common/tests/test_idatetime.py (+1/-1)
src/zope/interface/common/tests/test_import_interfaces.py (+1/-1)
src/zope/interface/declarations.py (+67/-57)
src/zope/interface/document.py (+2/-2)
src/zope/interface/exceptions.py (+1/-1)
src/zope/interface/interface.py (+38/-29)
src/zope/interface/interfaces.py (+11/-2)
src/zope/interface/ro.py (+1/-1)
src/zope/interface/tests/__init__.py (+13/-2)
src/zope/interface/tests/dummy.py (+2/-3)
src/zope/interface/tests/ifoo.py (+2/-2)
src/zope/interface/tests/m1.py (+1/-1)
src/zope/interface/tests/m2.py (+1/-1)
src/zope/interface/tests/odd.py (+8/-6)
src/zope/interface/tests/test_adapter.py (+3/-4)
src/zope/interface/tests/test_advice.py (+16/-7)
src/zope/interface/tests/test_declarations.py (+34/-37)
src/zope/interface/tests/test_document.py (+2/-2)
src/zope/interface/tests/test_element.py (+1/-1)
src/zope/interface/tests/test_interface.py (+164/-84)
src/zope/interface/tests/test_odd_declarations.py (+30/-18)
src/zope/interface/tests/test_sorting.py (+4/-4)
src/zope/interface/tests/test_verify.py (+6/-7)
src/zope/interface/tests/unitfixtures.py (+2/-2)
src/zope/interface/verify.py (+9/-4)
src/zope/interface/verify.txt (+25/-12)
src/zope/lifecycleevent/tests.py (+5/-2)
src/zope/pagetemplate/engine.py (+6/-6)
src/zope/pagetemplate/tests/test_engine.py (+2/-2)
src/zope/password/__init__.py (+2/-2)
src/zope/password/interfaces.py (+2/-2)
src/zope/password/password.py (+22/-10)
src/zope/password/testing.py (+2/-2)
src/zope/password/tests.py (+0/-28)
src/zope/password/tests/__init__.py (+16/-0)
src/zope/password/tests/test_password.py (+27/-0)
src/zope/password/tests/test_zpasswd.py (+157/-0)
src/zope/password/zpasswd.py (+280/-0)
src/zope/principalannotation/README.txt (+1/-0)
src/zope/principalannotation/interfaces.py (+2/-2)
src/zope/principalannotation/tests.py (+2/-2)
src/zope/principalannotation/utility.py (+8/-5)
src/zope/proxy/DEPENDENCIES.cfg (+0/-2)
src/zope/proxy/SETUP.cfg (+0/-8)
src/zope/proxy/tests/test_decorator.py (+2/-2)
src/zope/proxy/tests/test_proxy.py (+2/-2)
src/zope/publisher/http.py (+9/-8)
src/zope/publisher/publish.py (+7/-2)
src/zope/publisher/skinnable.txt (+330/-330)
src/zope/publisher/tests/test_browser.py (+2/-2)
src/zope/publisher/tests/test_defaultview.py (+1/-1)
src/zope/publisher/tests/test_http.py (+46/-8)
src/zope/publisher/tests/test_paste.py (+2/-1)
src/zope/publisher/tests/test_publisher.py (+41/-3)
src/zope/publisher/tests/test_skinnable.py (+2/-2)
src/zope/publisher/tests/test_xmlrpc.py (+2/-2)
src/zope/publisher/xmlrpc.py (+10/-5)
src/zope/schema/__init__.py (+1/-3)
src/zope/schema/_bootstrapfields.py (+1/-3)
src/zope/schema/_bootstrapinterfaces.py (+2/-5)
src/zope/schema/_field.py (+34/-17)
src/zope/schema/_messageid.py (+20/-0)
src/zope/schema/_schema.py (+1/-3)
src/zope/schema/accessors.py (+1/-3)
src/zope/schema/fieldproperty.py (+6/-8)
src/zope/schema/interfaces.py (+20/-12)
src/zope/schema/tests/states.py (+1/-3)
src/zope/schema/tests/test_accessors.py (+1/-3)
src/zope/schema/tests/test_boolfield.py (+1/-3)
src/zope/schema/tests/test_choice.py (+1/-3)
src/zope/schema/tests/test_containerfield.py (+1/-3)
src/zope/schema/tests/test_date.py (+1/-3)
src/zope/schema/tests/test_datetime.py (+1/-3)
src/zope/schema/tests/test_decimalfield.py (+1/-3)
src/zope/schema/tests/test_dictfield.py (+1/-3)
src/zope/schema/tests/test_docs.py (+5/-8)
src/zope/schema/tests/test_equality.py (+1/-3)
src/zope/schema/tests/test_field.py (+5/-8)
src/zope/schema/tests/test_fieldproperty.py (+1/-3)
src/zope/schema/tests/test_floatfield.py (+1/-3)
src/zope/schema/tests/test_interfacefield.py (+1/-3)
src/zope/schema/tests/test_intfield.py (+1/-3)
src/zope/schema/tests/test_iterablefield.py (+1/-3)
src/zope/schema/tests/test_listfield.py (+1/-3)
src/zope/schema/tests/test_objectfield.py (+121/-6)
src/zope/schema/tests/test_schema.py (+1/-3)
src/zope/schema/tests/test_setfield.py (+1/-3)
src/zope/schema/tests/test_states.py (+1/-3)
src/zope/schema/tests/test_strfield.py (+1/-3)
src/zope/schema/tests/test_timedelta.py (+1/-3)
src/zope/schema/tests/test_tuplefield.py (+1/-3)
src/zope/schema/tests/test_vocabulary.py (+1/-3)
src/zope/schema/vocabulary.py (+1/-3)
src/zope/security/checker.py (+7/-2)
src/zope/security/permission.py (+2/-2)
src/zope/security/tests/test_adapter.py (+2/-6)
src/zope/security/tests/test_decorator.py (+2/-3)
src/zope/security/tests/test_directives.py (+2/-2)
src/zope/security/tests/test_location.py (+1/-2)
src/zope/security/tests/test_module_directives.py (+3/-2)
src/zope/security/tests/test_permission.py (+3/-5)
src/zope/security/tests/test_proxy.py (+3/-7)
src/zope/security/tests/test_set_checkers.py (+3/-6)
src/zope/security/tests/test_standard_checkers.py (+1/-10)
src/zope/security/untrustedpython/tests.py (+12/-12)
src/zope/sendmail/maildir.py (+9/-2)
src/zope/sendmail/tests/test_delivery.py (+2/-2)
src/zope/sendmail/tests/test_maildir.py (+15/-1)
src/zope/sendmail/tests/test_vocabulary.py (+2/-2)
src/zope/server/__init__.py (+1/-3)
src/zope/server/adjustments.py (+1/-3)
src/zope/server/buffers.py (+1/-3)
src/zope/server/dualmodechannel.py (+1/-3)
src/zope/server/fixedstreamreceiver.py (+1/-3)
src/zope/server/ftp/logger.py (+1/-3)
src/zope/server/ftp/publisher.py (+1/-3)
src/zope/server/ftp/server.py (+1/-3)
src/zope/server/ftp/tests/demofs.py (+1/-3)
src/zope/server/ftp/tests/fstests.py (+1/-3)
src/zope/server/ftp/tests/test_demofs.py (+1/-3)
src/zope/server/ftp/tests/test_ftpserver.py (+1/-3)
src/zope/server/ftp/tests/test_publisher.py (+1/-3)
src/zope/server/http/chunking.py (+1/-3)
src/zope/server/http/commonaccesslogger.py (+1/-3)
src/zope/server/http/http_date.py (+1/-3)
src/zope/server/http/httprequestparser.py (+1/-3)
src/zope/server/http/httpserver.py (+1/-3)
src/zope/server/http/httpserverchannel.py (+1/-3)
src/zope/server/http/httptask.py (+1/-3)
src/zope/server/http/publisherhttpserver.py (+1/-3)
src/zope/server/http/tests/test_commonaccesslogger.py (+1/-3)
src/zope/server/http/tests/test_httpdate.py (+1/-3)
src/zope/server/http/tests/test_httprequestparser.py (+1/-3)
src/zope/server/http/tests/test_httpserver.py (+1/-3)
src/zope/server/http/tests/test_wsgiserver.py (+1/-3)
src/zope/server/http/wsgihttpserver.py (+1/-3)
src/zope/server/interfaces/__init__.py (+1/-3)
src/zope/server/interfaces/ftp.py (+1/-3)
src/zope/server/interfaces/logger.py (+1/-3)
src/zope/server/linereceiver/linecommandparser.py (+1/-3)
src/zope/server/linereceiver/lineserverchannel.py (+1/-3)
src/zope/server/linereceiver/linetask.py (+1/-3)
src/zope/server/logger/filelogger.py (+1/-3)
src/zope/server/logger/pythonlogger.py (+1/-3)
src/zope/server/logger/resolvinglogger.py (+1/-3)
src/zope/server/logger/rotatingfilelogger.py (+1/-3)
src/zope/server/logger/socketlogger.py (+1/-3)
src/zope/server/logger/sysloglogger.py (+1/-3)
src/zope/server/logger/taillogger.py (+1/-3)
src/zope/server/logger/tests/test_pythonlogger.py (+1/-3)
src/zope/server/logger/unresolvinglogger.py (+1/-3)
src/zope/server/maxsockets.py (+1/-3)
src/zope/server/serverbase.py (+1/-3)
src/zope/server/serverchannelbase.py (+1/-3)
src/zope/server/taskthreads.py (+6/-5)
src/zope/server/tests/asyncerror.py (+1/-3)
src/zope/server/tests/test_serverbase.py (+1/-3)
src/zope/server/trigger.py (+1/-1)
src/zope/server/utilities.py (+1/-3)
src/zope/server/zlogintegration.py (+1/-3)
src/zope/site/tests/test_folder.py (+2/-3)
src/zope/site/tests/test_registration.py (+6/-5)
src/zope/site/tests/test_site.py (+3/-2)
src/zope/structuredtext/STNG.txt (+0/-111)
src/zope/structuredtext/TODO.txt (+0/-6)
src/zope/structuredtext/__init__.py (+15/-12)
src/zope/structuredtext/docbook.py (+40/-24)
src/zope/structuredtext/document.py (+240/-168)
src/zope/structuredtext/html.py (+81/-49)
src/zope/structuredtext/regressions/Acquisition.ref (+18/-18)
src/zope/structuredtext/regressions/ExtensionClass.ref (+18/-18)
src/zope/structuredtext/regressions/MultiMapping.ref (+10/-10)
src/zope/structuredtext/regressions/examples.ref (+2/-2)
src/zope/structuredtext/regressions/examples1.ref (+2/-2)
src/zope/structuredtext/stdom.py (+1/-3)
src/zope/structuredtext/stletters.py (+2/-3)
src/zope/structuredtext/stng.py (+5/-4)
src/zope/structuredtext/tests.py (+139/-29)
src/zope/tales/tests/test_tales.py (+2/-2)
src/zope/testbrowser/README.txt (+36/-1)
src/zope/testbrowser/__init__.py (+2/-2)
src/zope/testbrowser/browser.py (+14/-14)
src/zope/testbrowser/cookies.py (+1/-1)
src/zope/testbrowser/fixed-bugs.txt (+1/-3)
src/zope/testbrowser/interfaces.py (+4/-4)
src/zope/testbrowser/testing.py (+12/-19)
src/zope/testbrowser/tests.py (+149/-101)
src/zope/traversing/tests/test_namespacetrversal.py (+2/-2)
src/zope/viewlet/__init__.py (+2/-2)
src/zope/viewlet/interfaces.py (+2/-2)
src/zope/viewlet/manager.py (+2/-2)
src/zope/viewlet/metaconfigure.py (+2/-2)
src/zope/viewlet/metadirectives.py (+3/-3)
src/zope/viewlet/tests.py (+10/-3)
src/zope/viewlet/viewlet.py (+2/-2)
To merge this branch: bzr merge lp:~sidnei/zope3/ztk-1.0a1
Reviewer Review Type Date Requested Status
Muharem Hrnjadovic (community) Approve
Thomas Herve (community) Approve
Review via email: mp+29032@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Thomas Herve (therve) wrote :

+1!

review: Approve
Revision history for this message
Muharem Hrnjadovic (al-maisan) wrote :

Looks good, +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'src/ClientForm.py'
2--- src/ClientForm.py 2010-03-01 20:29:16 +0000
3+++ src/ClientForm.py 1970-01-01 00:00:00 +0000
4@@ -1,3401 +0,0 @@
5-"""HTML form handling for web clients.
6-
7-ClientForm is a Python module for handling HTML forms on the client
8-side, useful for parsing HTML forms, filling them in and returning the
9-completed forms to the server. It has developed from a port of Gisle
10-Aas' Perl module HTML::Form, from the libwww-perl library, but the
11-interface is not the same.
12-
13-The most useful docstring is the one for HTMLForm.
14-
15-RFC 1866: HTML 2.0
16-RFC 1867: Form-based File Upload in HTML
17-RFC 2388: Returning Values from Forms: multipart/form-data
18-HTML 3.2 Specification, W3C Recommendation 14 January 1997 (for ISINDEX)
19-HTML 4.01 Specification, W3C Recommendation 24 December 1999
20-
21-
22-Copyright 2002-2007 John J. Lee <jjl@pobox.com>
23-Copyright 2005 Gary Poster
24-Copyright 2005 Zope Corporation
25-Copyright 1998-2000 Gisle Aas.
26-
27-This code is free software; you can redistribute it and/or modify it
28-under the terms of the BSD or ZPL 2.1 licenses (see the file
29-COPYING.txt included with the distribution).
30-
31-"""
32-
33-# XXX
34-# Remove parser testing hack
35-# safeUrl()-ize action
36-# Switch to unicode throughout (would be 0.3.x)
37-# See Wichert Akkerman's 2004-01-22 message to c.l.py.
38-# Add charset parameter to Content-type headers? How to find value??
39-# Add some more functional tests
40-# Especially single and multiple file upload on the internet.
41-# Does file upload work when name is missing? Sourceforge tracker form
42-# doesn't like it. Check standards, and test with Apache. Test
43-# binary upload with Apache.
44-# mailto submission & enctype text/plain
45-# I'm not going to fix this unless somebody tells me what real servers
46-# that want this encoding actually expect: If enctype is
47-# application/x-www-form-urlencoded and there's a FILE control present.
48-# Strictly, it should be 'name=data' (see HTML 4.01 spec., section
49-# 17.13.2), but I send "name=" ATM. What about multiple file upload??
50-
51-# Would be nice, but I'm not going to do it myself:
52-# -------------------------------------------------
53-# Maybe a 0.4.x?
54-# Replace by_label etc. with moniker / selector concept. Allows, eg.,
55-# a choice between selection by value / id / label / element
56-# contents. Or choice between matching labels exactly or by
57-# substring. Etc.
58-# Remove deprecated methods.
59-# ...what else?
60-# Work on DOMForm.
61-# XForms? Don't know if there's a need here.
62-
63-__all__ = ['AmbiguityError', 'CheckboxControl', 'Control',
64- 'ControlNotFoundError', 'FileControl', 'FormParser', 'HTMLForm',
65- 'HiddenControl', 'IgnoreControl', 'ImageControl', 'IsindexControl',
66- 'Item', 'ItemCountError', 'ItemNotFoundError', 'Label',
67- 'ListControl', 'LocateError', 'Missing', 'ParseError', 'ParseFile',
68- 'ParseFileEx', 'ParseResponse', 'ParseResponseEx','PasswordControl',
69- 'RadioControl', 'ScalarControl', 'SelectControl',
70- 'SubmitButtonControl', 'SubmitControl', 'TextControl',
71- 'TextareaControl', 'XHTMLCompatibleFormParser']
72-
73-try: True
74-except NameError:
75- True = 1
76- False = 0
77-
78-try: bool
79-except NameError:
80- def bool(expr):
81- if expr: return True
82- else: return False
83-
84-try:
85- import logging
86- import inspect
87-except ImportError:
88- def debug(msg, *args, **kwds):
89- pass
90-else:
91- _logger = logging.getLogger("ClientForm")
92- OPTIMIZATION_HACK = True
93-
94- def debug(msg, *args, **kwds):
95- if OPTIMIZATION_HACK:
96- return
97-
98- caller_name = inspect.stack()[1][3]
99- extended_msg = '%%s %s' % msg
100- extended_args = (caller_name,)+args
101- debug = _logger.debug(extended_msg, *extended_args, **kwds)
102-
103- def _show_debug_messages():
104- global OPTIMIZATION_HACK
105- OPTIMIZATION_HACK = False
106- _logger.setLevel(logging.DEBUG)
107- handler = logging.StreamHandler(sys.stdout)
108- handler.setLevel(logging.DEBUG)
109- _logger.addHandler(handler)
110-
111-import sys, urllib, urllib2, types, mimetools, copy, urlparse, \
112- htmlentitydefs, re, random
113-from cStringIO import StringIO
114-
115-import sgmllib
116-# monkeypatch to fix http://www.python.org/sf/803422 :-(
117-sgmllib.charref = re.compile("&#(x?[0-9a-fA-F]+)[^0-9a-fA-F]")
118-
119-# HTMLParser.HTMLParser is recent, so live without it if it's not available
120-# (also, sgmllib.SGMLParser is much more tolerant of bad HTML)
121-try:
122- import HTMLParser
123-except ImportError:
124- HAVE_MODULE_HTMLPARSER = False
125-else:
126- HAVE_MODULE_HTMLPARSER = True
127-
128-try:
129- import warnings
130-except ImportError:
131- def deprecation(message, stack_offset=0):
132- pass
133-else:
134- def deprecation(message, stack_offset=0):
135- warnings.warn(message, DeprecationWarning, stacklevel=3+stack_offset)
136-
137-VERSION = "0.2.10"
138-
139-CHUNK = 1024 # size of chunks fed to parser, in bytes
140-
141-DEFAULT_ENCODING = "latin-1"
142-
143-class Missing: pass
144-
145-_compress_re = re.compile(r"\s+")
146-def compress_text(text): return _compress_re.sub(" ", text.strip())
147-
148-def normalize_line_endings(text):
149- return re.sub(r"(?:(?<!\r)\n)|(?:\r(?!\n))", "\r\n", text)
150-
151-
152-# This version of urlencode is from my Python 1.5.2 back-port of the
153-# Python 2.1 CVS maintenance branch of urllib. It will accept a sequence
154-# of pairs instead of a mapping -- the 2.0 version only accepts a mapping.
155-def urlencode(query,doseq=False,):
156- """Encode a sequence of two-element tuples or dictionary into a URL query \
157-string.
158-
159- If any values in the query arg are sequences and doseq is true, each
160- sequence element is converted to a separate parameter.
161-
162- If the query arg is a sequence of two-element tuples, the order of the
163- parameters in the output will match the order of parameters in the
164- input.
165- """
166-
167- if hasattr(query,"items"):
168- # mapping objects
169- query = query.items()
170- else:
171- # it's a bother at times that strings and string-like objects are
172- # sequences...
173- try:
174- # non-sequence items should not work with len()
175- x = len(query)
176- # non-empty strings will fail this
177- if len(query) and type(query[0]) != types.TupleType:
178- raise TypeError()
179- # zero-length sequences of all types will get here and succeed,
180- # but that's a minor nit - since the original implementation
181- # allowed empty dicts that type of behavior probably should be
182- # preserved for consistency
183- except TypeError:
184- ty,va,tb = sys.exc_info()
185- raise TypeError("not a valid non-string sequence or mapping "
186- "object", tb)
187-
188- l = []
189- if not doseq:
190- # preserve old behavior
191- for k, v in query:
192- k = urllib.quote_plus(str(k))
193- v = urllib.quote_plus(str(v))
194- l.append(k + '=' + v)
195- else:
196- for k, v in query:
197- k = urllib.quote_plus(str(k))
198- if type(v) == types.StringType:
199- v = urllib.quote_plus(v)
200- l.append(k + '=' + v)
201- elif type(v) == types.UnicodeType:
202- # is there a reasonable way to convert to ASCII?
203- # encode generates a string, but "replace" or "ignore"
204- # lose information and "strict" can raise UnicodeError
205- v = urllib.quote_plus(v.encode("ASCII","replace"))
206- l.append(k + '=' + v)
207- else:
208- try:
209- # is this a sufficient test for sequence-ness?
210- x = len(v)
211- except TypeError:
212- # not a sequence
213- v = urllib.quote_plus(str(v))
214- l.append(k + '=' + v)
215- else:
216- # loop over the sequence
217- for elt in v:
218- l.append(k + '=' + urllib.quote_plus(str(elt)))
219- return '&'.join(l)
220-
221-def unescape(data, entities, encoding=DEFAULT_ENCODING):
222- if data is None or "&" not in data:
223- return data
224-
225- def replace_entities(match, entities=entities, encoding=encoding):
226- ent = match.group()
227- if ent[1] == "#":
228- return unescape_charref(ent[2:-1], encoding)
229-
230- repl = entities.get(ent)
231- if repl is not None:
232- if type(repl) != type(""):
233- try:
234- repl = repl.encode(encoding)
235- except UnicodeError:
236- repl = ent
237- else:
238- repl = ent
239-
240- return repl
241-
242- return re.sub(r"&#?[A-Za-z0-9]+?;", replace_entities, data)
243-
244-def unescape_charref(data, encoding):
245- name, base = data, 10
246- if name.startswith("x"):
247- name, base= name[1:], 16
248- uc = unichr(int(name, base))
249- if encoding is None:
250- return uc
251- else:
252- try:
253- repl = uc.encode(encoding)
254- except UnicodeError:
255- repl = "&#%s;" % data
256- return repl
257-
258-def get_entitydefs():
259- import htmlentitydefs
260- from codecs import latin_1_decode
261- entitydefs = {}
262- try:
263- htmlentitydefs.name2codepoint
264- except AttributeError:
265- entitydefs = {}
266- for name, char in htmlentitydefs.entitydefs.items():
267- uc = latin_1_decode(char)[0]
268- if uc.startswith("&#") and uc.endswith(";"):
269- uc = unescape_charref(uc[2:-1], None)
270- entitydefs["&%s;" % name] = uc
271- else:
272- for name, codepoint in htmlentitydefs.name2codepoint.items():
273- entitydefs["&%s;" % name] = unichr(codepoint)
274- return entitydefs
275-
276-
277-def issequence(x):
278- try:
279- x[0]
280- except (TypeError, KeyError):
281- return False
282- except IndexError:
283- pass
284- return True
285-
286-def isstringlike(x):
287- try: x+""
288- except: return False
289- else: return True
290-
291-
292-def choose_boundary():
293- """Return a string usable as a multipart boundary."""
294- # follow IE and firefox
295- nonce = "".join([str(random.randint(0, sys.maxint-1)) for i in 0,1,2])
296- return "-"*27 + nonce
297-
298-# This cut-n-pasted MimeWriter from standard library is here so can add
299-# to HTTP headers rather than message body when appropriate. It also uses
300-# \r\n in place of \n. This is a bit nasty.
301-class MimeWriter:
302-
303- """Generic MIME writer.
304-
305- Methods:
306-
307- __init__()
308- addheader()
309- flushheaders()
310- startbody()
311- startmultipartbody()
312- nextpart()
313- lastpart()
314-
315- A MIME writer is much more primitive than a MIME parser. It
316- doesn't seek around on the output file, and it doesn't use large
317- amounts of buffer space, so you have to write the parts in the
318- order they should occur on the output file. It does buffer the
319- headers you add, allowing you to rearrange their order.
320-
321- General usage is:
322-
323- f = <open the output file>
324- w = MimeWriter(f)
325- ...call w.addheader(key, value) 0 or more times...
326-
327- followed by either:
328-
329- f = w.startbody(content_type)
330- ...call f.write(data) for body data...
331-
332- or:
333-
334- w.startmultipartbody(subtype)
335- for each part:
336- subwriter = w.nextpart()
337- ...use the subwriter's methods to create the subpart...
338- w.lastpart()
339-
340- The subwriter is another MimeWriter instance, and should be
341- treated in the same way as the toplevel MimeWriter. This way,
342- writing recursive body parts is easy.
343-
344- Warning: don't forget to call lastpart()!
345-
346- XXX There should be more state so calls made in the wrong order
347- are detected.
348-
349- Some special cases:
350-
351- - startbody() just returns the file passed to the constructor;
352- but don't use this knowledge, as it may be changed.
353-
354- - startmultipartbody() actually returns a file as well;
355- this can be used to write the initial 'if you can read this your
356- mailer is not MIME-aware' message.
357-
358- - If you call flushheaders(), the headers accumulated so far are
359- written out (and forgotten); this is useful if you don't need a
360- body part at all, e.g. for a subpart of type message/rfc822
361- that's (mis)used to store some header-like information.
362-
363- - Passing a keyword argument 'prefix=<flag>' to addheader(),
364- start*body() affects where the header is inserted; 0 means
365- append at the end, 1 means insert at the start; default is
366- append for addheader(), but insert for start*body(), which use
367- it to determine where the Content-type header goes.
368-
369- """
370-
371- def __init__(self, fp, http_hdrs=None):
372- self._http_hdrs = http_hdrs
373- self._fp = fp
374- self._headers = []
375- self._boundary = []
376- self._first_part = True
377-
378- def addheader(self, key, value, prefix=0,
379- add_to_http_hdrs=0):
380- """
381- prefix is ignored if add_to_http_hdrs is true.
382- """
383- lines = value.split("\r\n")
384- while lines and not lines[-1]: del lines[-1]
385- while lines and not lines[0]: del lines[0]
386- if add_to_http_hdrs:
387- value = "".join(lines)
388- # 2.2 urllib2 doesn't normalize header case
389- self._http_hdrs.append((key.capitalize(), value))
390- else:
391- for i in range(1, len(lines)):
392- lines[i] = " " + lines[i].strip()
393- value = "\r\n".join(lines) + "\r\n"
394- line = key.title() + ": " + value
395- if prefix:
396- self._headers.insert(0, line)
397- else:
398- self._headers.append(line)
399-
400- def flushheaders(self):
401- self._fp.writelines(self._headers)
402- self._headers = []
403-
404- def startbody(self, ctype=None, plist=[], prefix=1,
405- add_to_http_hdrs=0, content_type=1):
406- """
407- prefix is ignored if add_to_http_hdrs is true.
408- """
409- if content_type and ctype:
410- for name, value in plist:
411- ctype = ctype + ';\r\n %s=%s' % (name, value)
412- self.addheader("Content-Type", ctype, prefix=prefix,
413- add_to_http_hdrs=add_to_http_hdrs)
414- self.flushheaders()
415- if not add_to_http_hdrs: self._fp.write("\r\n")
416- self._first_part = True
417- return self._fp
418-
419- def startmultipartbody(self, subtype, boundary=None, plist=[], prefix=1,
420- add_to_http_hdrs=0, content_type=1):
421- boundary = boundary or choose_boundary()
422- self._boundary.append(boundary)
423- return self.startbody("multipart/" + subtype,
424- [("boundary", boundary)] + plist,
425- prefix=prefix,
426- add_to_http_hdrs=add_to_http_hdrs,
427- content_type=content_type)
428-
429- def nextpart(self):
430- boundary = self._boundary[-1]
431- if self._first_part:
432- self._first_part = False
433- else:
434- self._fp.write("\r\n")
435- self._fp.write("--" + boundary + "\r\n")
436- return self.__class__(self._fp)
437-
438- def lastpart(self):
439- if self._first_part:
440- self.nextpart()
441- boundary = self._boundary.pop()
442- self._fp.write("\r\n--" + boundary + "--\r\n")
443-
444-
445-class LocateError(ValueError): pass
446-class AmbiguityError(LocateError): pass
447-class ControlNotFoundError(LocateError): pass
448-class ItemNotFoundError(LocateError): pass
449-
450-class ItemCountError(ValueError): pass
451-
452-# for backwards compatibility, ParseError derives from exceptions that were
453-# raised by versions of ClientForm <= 0.2.5
454-if HAVE_MODULE_HTMLPARSER:
455- SGMLLIB_PARSEERROR = sgmllib.SGMLParseError
456- class ParseError(sgmllib.SGMLParseError,
457- HTMLParser.HTMLParseError,
458- ):
459- pass
460-else:
461- if hasattr(sgmllib, "SGMLParseError"):
462- SGMLLIB_PARSEERROR = sgmllib.SGMLParseError
463- class ParseError(sgmllib.SGMLParseError):
464- pass
465- else:
466- SGMLLIB_PARSEERROR = RuntimeError
467- class ParseError(RuntimeError):
468- pass
469-
470-
471-class _AbstractFormParser:
472- """forms attribute contains HTMLForm instances on completion."""
473- # thanks to Moshe Zadka for an example of sgmllib/htmllib usage
474- def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
475- if entitydefs is None:
476- entitydefs = get_entitydefs()
477- self._entitydefs = entitydefs
478- self._encoding = encoding
479-
480- self.base = None
481- self.forms = []
482- self.labels = []
483- self._current_label = None
484- self._current_form = None
485- self._select = None
486- self._optgroup = None
487- self._option = None
488- self._textarea = None
489-
490- # forms[0] will contain all controls that are outside of any form
491- # self._global_form is an alias for self.forms[0]
492- self._global_form = None
493- self.start_form([])
494- self.end_form()
495- self._current_form = self._global_form = self.forms[0]
496-
497- def do_base(self, attrs):
498- debug("%s", attrs)
499- for key, value in attrs:
500- if key == "href":
501- self.base = self.unescape_attr_if_required(value)
502-
503- def end_body(self):
504- debug("")
505- if self._current_label is not None:
506- self.end_label()
507- if self._current_form is not self._global_form:
508- self.end_form()
509-
510- def start_form(self, attrs):
511- debug("%s", attrs)
512- if self._current_form is not self._global_form:
513- raise ParseError("nested FORMs")
514- name = None
515- action = None
516- enctype = "application/x-www-form-urlencoded"
517- method = "GET"
518- d = {}
519- for key, value in attrs:
520- if key == "name":
521- name = self.unescape_attr_if_required(value)
522- elif key == "action":
523- action = self.unescape_attr_if_required(value)
524- elif key == "method":
525- method = self.unescape_attr_if_required(value.upper())
526- elif key == "enctype":
527- enctype = self.unescape_attr_if_required(value.lower())
528- d[key] = self.unescape_attr_if_required(value)
529- controls = []
530- self._current_form = (name, action, method, enctype), d, controls
531-
532- def end_form(self):
533- debug("")
534- if self._current_label is not None:
535- self.end_label()
536- if self._current_form is self._global_form:
537- raise ParseError("end of FORM before start")
538- self.forms.append(self._current_form)
539- self._current_form = self._global_form
540-
541- def start_select(self, attrs):
542- debug("%s", attrs)
543- if self._select is not None:
544- raise ParseError("nested SELECTs")
545- if self._textarea is not None:
546- raise ParseError("SELECT inside TEXTAREA")
547- d = {}
548- for key, val in attrs:
549- d[key] = self.unescape_attr_if_required(val)
550-
551- self._select = d
552- self._add_label(d)
553-
554- self._append_select_control({"__select": d})
555-
556- def end_select(self):
557- debug("")
558- if self._select is None:
559- raise ParseError("end of SELECT before start")
560-
561- if self._option is not None:
562- self._end_option()
563-
564- self._select = None
565-
566- def start_optgroup(self, attrs):
567- debug("%s", attrs)
568- if self._select is None:
569- raise ParseError("OPTGROUP outside of SELECT")
570- d = {}
571- for key, val in attrs:
572- d[key] = self.unescape_attr_if_required(val)
573-
574- self._optgroup = d
575-
576- def end_optgroup(self):
577- debug("")
578- if self._optgroup is None:
579- raise ParseError("end of OPTGROUP before start")
580- self._optgroup = None
581-
582- def _start_option(self, attrs):
583- debug("%s", attrs)
584- if self._select is None:
585- raise ParseError("OPTION outside of SELECT")
586- if self._option is not None:
587- self._end_option()
588-
589- d = {}
590- for key, val in attrs:
591- d[key] = self.unescape_attr_if_required(val)
592-
593- self._option = {}
594- self._option.update(d)
595- if (self._optgroup and self._optgroup.has_key("disabled") and
596- not self._option.has_key("disabled")):
597- self._option["disabled"] = None
598-
599- def _end_option(self):
600- debug("")
601- if self._option is None:
602- raise ParseError("end of OPTION before start")
603-
604- contents = self._option.get("contents", "").strip()
605- self._option["contents"] = contents
606- if not self._option.has_key("value"):
607- self._option["value"] = contents
608- if not self._option.has_key("label"):
609- self._option["label"] = contents
610- # stuff dict of SELECT HTML attrs into a special private key
611- # (gets deleted again later)
612- self._option["__select"] = self._select
613- self._append_select_control(self._option)
614- self._option = None
615-
616- def _append_select_control(self, attrs):
617- debug("%s", attrs)
618- controls = self._current_form[2]
619- name = self._select.get("name")
620- controls.append(("select", name, attrs))
621-
622- def start_textarea(self, attrs):
623- debug("%s", attrs)
624- if self._textarea is not None:
625- raise ParseError("nested TEXTAREAs")
626- if self._select is not None:
627- raise ParseError("TEXTAREA inside SELECT")
628- d = {}
629- for key, val in attrs:
630- d[key] = self.unescape_attr_if_required(val)
631- self._add_label(d)
632-
633- self._textarea = d
634-
635- def end_textarea(self):
636- debug("")
637- if self._textarea is None:
638- raise ParseError("end of TEXTAREA before start")
639- controls = self._current_form[2]
640- name = self._textarea.get("name")
641- controls.append(("textarea", name, self._textarea))
642- self._textarea = None
643-
644- def start_label(self, attrs):
645- debug("%s", attrs)
646- if self._current_label:
647- self.end_label()
648- d = {}
649- for key, val in attrs:
650- d[key] = self.unescape_attr_if_required(val)
651- taken = bool(d.get("for")) # empty id is invalid
652- d["__text"] = ""
653- d["__taken"] = taken
654- if taken:
655- self.labels.append(d)
656- self._current_label = d
657-
658- def end_label(self):
659- debug("")
660- label = self._current_label
661- if label is None:
662- # something is ugly in the HTML, but we're ignoring it
663- return
664- self._current_label = None
665- # if it is staying around, it is True in all cases
666- del label["__taken"]
667-
668- def _add_label(self, d):
669- #debug("%s", d)
670- if self._current_label is not None:
671- if not self._current_label["__taken"]:
672- self._current_label["__taken"] = True
673- d["__label"] = self._current_label
674-
675- def handle_data(self, data):
676- debug("%s", data)
677-
678- if self._option is not None:
679- # self._option is a dictionary of the OPTION element's HTML
680- # attributes, but it has two special keys, one of which is the
681- # special "contents" key contains text between OPTION tags (the
682- # other is the "__select" key: see the end_option method)
683- map = self._option
684- key = "contents"
685- elif self._textarea is not None:
686- map = self._textarea
687- key = "value"
688- data = normalize_line_endings(data)
689- # not if within option or textarea
690- elif self._current_label is not None:
691- map = self._current_label
692- key = "__text"
693- else:
694- return
695-
696- if data and not map.has_key(key):
697- # according to
698- # http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1 line break
699- # immediately after start tags or immediately before end tags must
700- # be ignored, but real browsers only ignore a line break after a
701- # start tag, so we'll do that.
702- if data[0:2] == "\r\n":
703- data = data[2:]
704- elif data[0:1] in ["\n", "\r"]:
705- data = data[1:]
706- map[key] = data
707- else:
708- map[key] = map[key] + data
709-
710- def do_button(self, attrs):
711- debug("%s", attrs)
712- d = {}
713- d["type"] = "submit" # default
714- for key, val in attrs:
715- d[key] = self.unescape_attr_if_required(val)
716- controls = self._current_form[2]
717-
718- type = d["type"]
719- name = d.get("name")
720- # we don't want to lose information, so use a type string that
721- # doesn't clash with INPUT TYPE={SUBMIT,RESET,BUTTON}
722- # e.g. type for BUTTON/RESET is "resetbutton"
723- # (type for INPUT/RESET is "reset")
724- type = type+"button"
725- self._add_label(d)
726- controls.append((type, name, d))
727-
728- def do_input(self, attrs):
729- debug("%s", attrs)
730- d = {}
731- d["type"] = "text" # default
732- for key, val in attrs:
733- d[key] = self.unescape_attr_if_required(val)
734- controls = self._current_form[2]
735-
736- type = d["type"]
737- name = d.get("name")
738- self._add_label(d)
739- controls.append((type, name, d))
740-
741- def do_isindex(self, attrs):
742- debug("%s", attrs)
743- d = {}
744- for key, val in attrs:
745- d[key] = self.unescape_attr_if_required(val)
746- controls = self._current_form[2]
747-
748- self._add_label(d)
749- # isindex doesn't have type or name HTML attributes
750- controls.append(("isindex", None, d))
751-
752- def handle_entityref(self, name):
753- #debug("%s", name)
754- self.handle_data(unescape(
755- '&%s;' % name, self._entitydefs, self._encoding))
756-
757- def handle_charref(self, name):
758- #debug("%s", name)
759- self.handle_data(unescape_charref(name, self._encoding))
760-
761- def unescape_attr(self, name):
762- #debug("%s", name)
763- return unescape(name, self._entitydefs, self._encoding)
764-
765- def unescape_attrs(self, attrs):
766- #debug("%s", attrs)
767- escaped_attrs = {}
768- for key, val in attrs.items():
769- try:
770- val.items
771- except AttributeError:
772- escaped_attrs[key] = self.unescape_attr(val)
773- else:
774- # e.g. "__select" -- yuck!
775- escaped_attrs[key] = self.unescape_attrs(val)
776- return escaped_attrs
777-
778- def unknown_entityref(self, ref): self.handle_data("&%s;" % ref)
779- def unknown_charref(self, ref): self.handle_data("&#%s;" % ref)
780-
781-
782-if not HAVE_MODULE_HTMLPARSER:
783- class XHTMLCompatibleFormParser:
784- def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
785- raise ValueError("HTMLParser could not be imported")
786-else:
787- class XHTMLCompatibleFormParser(_AbstractFormParser, HTMLParser.HTMLParser):
788- """Good for XHTML, bad for tolerance of incorrect HTML."""
789- # thanks to Michael Howitz for this!
790- def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
791- HTMLParser.HTMLParser.__init__(self)
792- _AbstractFormParser.__init__(self, entitydefs, encoding)
793-
794- def feed(self, data):
795- try:
796- HTMLParser.HTMLParser.feed(self, data)
797- except HTMLParser.HTMLParseError, exc:
798- raise ParseError(exc)
799-
800- def start_option(self, attrs):
801- _AbstractFormParser._start_option(self, attrs)
802-
803- def end_option(self):
804- _AbstractFormParser._end_option(self)
805-
806- def handle_starttag(self, tag, attrs):
807- try:
808- method = getattr(self, "start_" + tag)
809- except AttributeError:
810- try:
811- method = getattr(self, "do_" + tag)
812- except AttributeError:
813- pass # unknown tag
814- else:
815- method(attrs)
816- else:
817- method(attrs)
818-
819- def handle_endtag(self, tag):
820- try:
821- method = getattr(self, "end_" + tag)
822- except AttributeError:
823- pass # unknown tag
824- else:
825- method()
826-
827- def unescape(self, name):
828- # Use the entitydefs passed into constructor, not
829- # HTMLParser.HTMLParser's entitydefs.
830- return self.unescape_attr(name)
831-
832- def unescape_attr_if_required(self, name):
833- return name # HTMLParser.HTMLParser already did it
834- def unescape_attrs_if_required(self, attrs):
835- return attrs # ditto
836-
837- def close(self):
838- HTMLParser.HTMLParser.close(self)
839- self.end_body()
840-
841-
842-class _AbstractSgmllibParser(_AbstractFormParser):
843-
844- def do_option(self, attrs):
845- _AbstractFormParser._start_option(self, attrs)
846-
847- if sys.version_info[:2] >= (2,5):
848- # we override this attr to decode hex charrefs
849- entity_or_charref = re.compile(
850- '&(?:([a-zA-Z][-.a-zA-Z0-9]*)|#(x?[0-9a-fA-F]+))(;?)')
851- def convert_entityref(self, name):
852- return unescape("&%s;" % name, self._entitydefs, self._encoding)
853- def convert_charref(self, name):
854- return unescape_charref("%s" % name, self._encoding)
855- def unescape_attr_if_required(self, name):
856- return name # sgmllib already did it
857- def unescape_attrs_if_required(self, attrs):
858- return attrs # ditto
859- else:
860- def unescape_attr_if_required(self, name):
861- return self.unescape_attr(name)
862- def unescape_attrs_if_required(self, attrs):
863- return self.unescape_attrs(attrs)
864-
865-
866-class FormParser(_AbstractSgmllibParser, sgmllib.SGMLParser):
867- """Good for tolerance of incorrect HTML, bad for XHTML."""
868- def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
869- sgmllib.SGMLParser.__init__(self)
870- _AbstractFormParser.__init__(self, entitydefs, encoding)
871-
872- def feed(self, data):
873- try:
874- sgmllib.SGMLParser.feed(self, data)
875- except SGMLLIB_PARSEERROR, exc:
876- raise ParseError(exc)
877-
878- def close(self):
879- sgmllib.SGMLParser.close(self)
880- self.end_body()
881-
882-
883-# sigh, must support mechanize by allowing dynamic creation of classes based on
884-# its bundled copy of BeautifulSoup (which was necessary because of dependency
885-# problems)
886-
887-def _create_bs_classes(bs,
888- icbinbs,
889- ):
890- class _AbstractBSFormParser(_AbstractSgmllibParser):
891- bs_base_class = None
892- def __init__(self, entitydefs=None, encoding=DEFAULT_ENCODING):
893- _AbstractFormParser.__init__(self, entitydefs, encoding)
894- self.bs_base_class.__init__(self)
895- def handle_data(self, data):
896- _AbstractFormParser.handle_data(self, data)
897- self.bs_base_class.handle_data(self, data)
898- def feed(self, data):
899- try:
900- self.bs_base_class.feed(self, data)
901- except SGMLLIB_PARSEERROR, exc:
902- raise ParseError(exc)
903- def close(self):
904- self.bs_base_class.close(self)
905- self.end_body()
906-
907- class RobustFormParser(_AbstractBSFormParser, bs):
908- """Tries to be highly tolerant of incorrect HTML."""
909- pass
910- RobustFormParser.bs_base_class = bs
911- class NestingRobustFormParser(_AbstractBSFormParser, icbinbs):
912- """Tries to be highly tolerant of incorrect HTML.
913-
914- Different from RobustFormParser in that it more often guesses nesting
915- above missing end tags (see BeautifulSoup docs).
916-
917- """
918- pass
919- NestingRobustFormParser.bs_base_class = icbinbs
920-
921- return RobustFormParser, NestingRobustFormParser
922-
923-try:
924- if sys.version_info[:2] < (2, 2):
925- raise ImportError # BeautifulSoup uses generators
926- import BeautifulSoup
927-except ImportError:
928- pass
929-else:
930- RobustFormParser, NestingRobustFormParser = _create_bs_classes(
931- BeautifulSoup.BeautifulSoup, BeautifulSoup.ICantBelieveItsBeautifulSoup
932- )
933- __all__ += ['RobustFormParser', 'NestingRobustFormParser']
934-
935-
936-#FormParser = XHTMLCompatibleFormParser # testing hack
937-#FormParser = RobustFormParser # testing hack
938-
939-
940-def ParseResponseEx(response,
941- select_default=False,
942- form_parser_class=FormParser,
943- request_class=urllib2.Request,
944- entitydefs=None,
945- encoding=DEFAULT_ENCODING,
946-
947- # private
948- _urljoin=urlparse.urljoin,
949- _urlparse=urlparse.urlparse,
950- _urlunparse=urlparse.urlunparse,
951- ):
952- """Identical to ParseResponse, except that:
953-
954- 1. The returned list contains an extra item. The first form in the list
955- contains all controls not contained in any FORM element.
956-
957- 2. The arguments ignore_errors and backwards_compat have been removed.
958-
959- 3. Backwards-compatibility mode (backwards_compat=True) is not available.
960- """
961- return _ParseFileEx(response, response.geturl(),
962- select_default,
963- False,
964- form_parser_class,
965- request_class,
966- entitydefs,
967- False,
968- encoding,
969- _urljoin=_urljoin,
970- _urlparse=_urlparse,
971- _urlunparse=_urlunparse,
972- )
973-
974-def ParseFileEx(file, base_uri,
975- select_default=False,
976- form_parser_class=FormParser,
977- request_class=urllib2.Request,
978- entitydefs=None,
979- encoding=DEFAULT_ENCODING,
980-
981- # private
982- _urljoin=urlparse.urljoin,
983- _urlparse=urlparse.urlparse,
984- _urlunparse=urlparse.urlunparse,
985- ):
986- """Identical to ParseFile, except that:
987-
988- 1. The returned list contains an extra item. The first form in the list
989- contains all controls not contained in any FORM element.
990-
991- 2. The arguments ignore_errors and backwards_compat have been removed.
992-
993- 3. Backwards-compatibility mode (backwards_compat=True) is not available.
994- """
995- return _ParseFileEx(file, base_uri,
996- select_default,
997- False,
998- form_parser_class,
999- request_class,
1000- entitydefs,
1001- False,
1002- encoding,
1003- _urljoin=_urljoin,
1004- _urlparse=_urlparse,
1005- _urlunparse=_urlunparse,
1006- )
1007-
1008-def ParseResponse(response, *args, **kwds):
1009- """Parse HTTP response and return a list of HTMLForm instances.
1010-
1011- The return value of urllib2.urlopen can be conveniently passed to this
1012- function as the response parameter.
1013-
1014- ClientForm.ParseError is raised on parse errors.
1015-
1016- response: file-like object (supporting read() method) with a method
1017- geturl(), returning the URI of the HTTP response
1018- select_default: for multiple-selection SELECT controls and RADIO controls,
1019- pick the first item as the default if none are selected in the HTML
1020- form_parser_class: class to instantiate and use to pass
1021- request_class: class to return from .click() method (default is
1022- urllib2.Request)
1023- entitydefs: mapping like {"&amp;": "&", ...} containing HTML entity
1024- definitions (a sensible default is used)
1025- encoding: character encoding used for encoding numeric character references
1026- when matching link text. ClientForm does not attempt to find the encoding
1027- in a META HTTP-EQUIV attribute in the document itself (mechanize, for
1028- example, does do that and will pass the correct value to ClientForm using
1029- this parameter).
1030-
1031- backwards_compat: boolean that determines whether the returned HTMLForm
1032- objects are backwards-compatible with old code. If backwards_compat is
1033- true:
1034-
1035- - ClientForm 0.1 code will continue to work as before.
1036-
1037- - Label searches that do not specify a nr (number or count) will always
1038- get the first match, even if other controls match. If
1039- backwards_compat is False, label searches that have ambiguous results
1040- will raise an AmbiguityError.
1041-
1042- - Item label matching is done by strict string comparison rather than
1043- substring matching.
1044-
1045- - De-selecting individual list items is allowed even if the Item is
1046- disabled.
1047-
1048- The backwards_compat argument will be deprecated in a future release.
1049-
1050- Pass a true value for select_default if you want the behaviour specified by
1051- RFC 1866 (the HTML 2.0 standard), which is to select the first item in a
1052- RADIO or multiple-selection SELECT control if none were selected in the
1053- HTML. Most browsers (including Microsoft Internet Explorer (IE) and
1054- Netscape Navigator) instead leave all items unselected in these cases. The
1055- W3C HTML 4.0 standard leaves this behaviour undefined in the case of
1056- multiple-selection SELECT controls, but insists that at least one RADIO
1057- button should be checked at all times, in contradiction to browser
1058- behaviour.
1059-
1060- There is a choice of parsers. ClientForm.XHTMLCompatibleFormParser (uses
1061- HTMLParser.HTMLParser) works best for XHTML, ClientForm.FormParser (uses
1062- sgmllib.SGMLParser) (the default) works better for ordinary grubby HTML.
1063- Note that HTMLParser is only available in Python 2.2 and later. You can
1064- pass your own class in here as a hack to work around bad HTML, but at your
1065- own risk: there is no well-defined interface.
1066-
1067- """
1068- return _ParseFileEx(response, response.geturl(), *args, **kwds)[1:]
1069-
1070-def ParseFile(file, base_uri, *args, **kwds):
1071- """Parse HTML and return a list of HTMLForm instances.
1072-
1073- ClientForm.ParseError is raised on parse errors.
1074-
1075- file: file-like object (supporting read() method) containing HTML with zero
1076- or more forms to be parsed
1077- base_uri: the URI of the document (note that the base URI used to submit
1078- the form will be that given in the BASE element if present, not that of
1079- the document)
1080-
1081- For the other arguments and further details, see ParseResponse.__doc__.
1082-
1083- """
1084- return _ParseFileEx(file, base_uri, *args, **kwds)[1:]
1085-
1086-def _ParseFileEx(file, base_uri,
1087- select_default=False,
1088- ignore_errors=False,
1089- form_parser_class=FormParser,
1090- request_class=urllib2.Request,
1091- entitydefs=None,
1092- backwards_compat=True,
1093- encoding=DEFAULT_ENCODING,
1094- _urljoin=urlparse.urljoin,
1095- _urlparse=urlparse.urlparse,
1096- _urlunparse=urlparse.urlunparse,
1097- ):
1098- if backwards_compat:
1099- deprecation("operating in backwards-compatibility mode", 1)
1100- fp = form_parser_class(entitydefs, encoding)
1101- while 1:
1102- data = file.read(CHUNK)
1103- try:
1104- fp.feed(data)
1105- except ParseError, e:
1106- e.base_uri = base_uri
1107- raise
1108- if len(data) != CHUNK: break
1109- fp.close()
1110- if fp.base is not None:
1111- # HTML BASE element takes precedence over document URI
1112- base_uri = fp.base
1113- labels = [] # Label(label) for label in fp.labels]
1114- id_to_labels = {}
1115- for l in fp.labels:
1116- label = Label(l)
1117- labels.append(label)
1118- for_id = l["for"]
1119- coll = id_to_labels.get(for_id)
1120- if coll is None:
1121- id_to_labels[for_id] = [label]
1122- else:
1123- coll.append(label)
1124- forms = []
1125- for (name, action, method, enctype), attrs, controls in fp.forms:
1126- if action is None:
1127- action = base_uri
1128- else:
1129- action = _urljoin(base_uri, action)
1130- # would be nice to make HTMLForm class (form builder) pluggable
1131- form = HTMLForm(
1132- action, method, enctype, name, attrs, request_class,
1133- forms, labels, id_to_labels, backwards_compat)
1134- form._urlparse = _urlparse
1135- form._urlunparse = _urlunparse
1136- for ii in range(len(controls)):
1137- type, name, attrs = controls[ii]
1138- # index=ii*10 allows ImageControl to return multiple ordered pairs
1139- form.new_control(
1140- type, name, attrs, select_default=select_default, index=ii*10)
1141- forms.append(form)
1142- for form in forms:
1143- form.fixup()
1144- return forms
1145-
1146-
1147-class Label:
1148- def __init__(self, attrs):
1149- self.id = attrs.get("for")
1150- self._text = attrs.get("__text").strip()
1151- self._ctext = compress_text(self._text)
1152- self.attrs = attrs
1153- self._backwards_compat = False # maintained by HTMLForm
1154-
1155- def __getattr__(self, name):
1156- if name == "text":
1157- if self._backwards_compat:
1158- return self._text
1159- else:
1160- return self._ctext
1161- return getattr(Label, name)
1162-
1163- def __setattr__(self, name, value):
1164- if name == "text":
1165- # don't see any need for this, so make it read-only
1166- raise AttributeError("text attribute is read-only")
1167- self.__dict__[name] = value
1168-
1169- def __str__(self):
1170- return "<Label(id=%r, text=%r)>" % (self.id, self.text)
1171-
1172-
1173-def _get_label(attrs):
1174- text = attrs.get("__label")
1175- if text is not None:
1176- return Label(text)
1177- else:
1178- return None
1179-
1180-class Control:
1181- """An HTML form control.
1182-
1183- An HTMLForm contains a sequence of Controls. The Controls in an HTMLForm
1184- are accessed using the HTMLForm.find_control method or the
1185- HTMLForm.controls attribute.
1186-
1187- Control instances are usually constructed using the ParseFile /
1188- ParseResponse functions. If you use those functions, you can ignore the
1189- rest of this paragraph. A Control is only properly initialised after the
1190- fixup method has been called. In fact, this is only strictly necessary for
1191- ListControl instances. This is necessary because ListControls are built up
1192- from ListControls each containing only a single item, and their initial
1193- value(s) can only be known after the sequence is complete.
1194-
1195- The types and values that are acceptable for assignment to the value
1196- attribute are defined by subclasses.
1197-
1198- If the disabled attribute is true, this represents the state typically
1199- represented by browsers by 'greying out' a control. If the disabled
1200- attribute is true, the Control will raise AttributeError if an attempt is
1201- made to change its value. In addition, the control will not be considered
1202- 'successful' as defined by the W3C HTML 4 standard -- ie. it will
1203- contribute no data to the return value of the HTMLForm.click* methods. To
1204- enable a control, set the disabled attribute to a false value.
1205-
1206- If the readonly attribute is true, the Control will raise AttributeError if
1207- an attempt is made to change its value. To make a control writable, set
1208- the readonly attribute to a false value.
1209-
1210- All controls have the disabled and readonly attributes, not only those that
1211- may have the HTML attributes of the same names.
1212-
1213- On assignment to the value attribute, the following exceptions are raised:
1214- TypeError, AttributeError (if the value attribute should not be assigned
1215- to, because the control is disabled, for example) and ValueError.
1216-
1217- If the name or value attributes are None, or the value is an empty list, or
1218- if the control is disabled, the control is not successful.
1219-
1220- Public attributes:
1221-
1222- type: string describing type of control (see the keys of the
1223- HTMLForm.type2class dictionary for the allowable values) (readonly)
1224- name: name of control (readonly)
1225- value: current value of control (subclasses may allow a single value, a
1226- sequence of values, or either)
1227- disabled: disabled state
1228- readonly: readonly state
1229- id: value of id HTML attribute
1230-
1231- """
1232- def __init__(self, type, name, attrs, index=None):
1233- """
1234- type: string describing type of control (see the keys of the
1235- HTMLForm.type2class dictionary for the allowable values)
1236- name: control name
1237- attrs: HTML attributes of control's HTML element
1238-
1239- """
1240- raise NotImplementedError()
1241-
1242- def add_to_form(self, form):
1243- self._form = form
1244- form.controls.append(self)
1245-
1246- def fixup(self):
1247- pass
1248-
1249- def is_of_kind(self, kind):
1250- raise NotImplementedError()
1251-
1252- def clear(self):
1253- raise NotImplementedError()
1254-
1255- def __getattr__(self, name): raise NotImplementedError()
1256- def __setattr__(self, name, value): raise NotImplementedError()
1257-
1258- def pairs(self):
1259- """Return list of (key, value) pairs suitable for passing to urlencode.
1260- """
1261- return [(k, v) for (i, k, v) in self._totally_ordered_pairs()]
1262-
1263- def _totally_ordered_pairs(self):
1264- """Return list of (key, value, index) tuples.
1265-
1266- Like pairs, but allows preserving correct ordering even where several
1267- controls are involved.
1268-
1269- """
1270- raise NotImplementedError()
1271-
1272- def _write_mime_data(self, mw, name, value):
1273- """Write data for a subitem of this control to a MimeWriter."""
1274- # called by HTMLForm
1275- mw2 = mw.nextpart()
1276- mw2.addheader("Content-Disposition",
1277- 'form-data; name="%s"' % name, 1)
1278- f = mw2.startbody(prefix=0)
1279- f.write(value)
1280-
1281- def __str__(self):
1282- raise NotImplementedError()
1283-
1284- def get_labels(self):
1285- """Return all labels (Label instances) for this control.
1286-
1287- If the control was surrounded by a <label> tag, that will be the first
1288- label; all other labels, connected by 'for' and 'id', are in the order
1289- that appear in the HTML.
1290-
1291- """
1292- res = []
1293- if self._label:
1294- res.append(self._label)
1295- if self.id:
1296- res.extend(self._form._id_to_labels.get(self.id, ()))
1297- return res
1298-
1299-
1300-#---------------------------------------------------
1301-class ScalarControl(Control):
1302- """Control whose value is not restricted to one of a prescribed set.
1303-
1304- Some ScalarControls don't accept any value attribute. Otherwise, takes a
1305- single value, which must be string-like.
1306-
1307- Additional read-only public attribute:
1308-
1309- attrs: dictionary mapping the names of original HTML attributes of the
1310- control to their values
1311-
1312- """
1313- def __init__(self, type, name, attrs, index=None):
1314- self._index = index
1315- self._label = _get_label(attrs)
1316- self.__dict__["type"] = type.lower()
1317- self.__dict__["name"] = name
1318- self._value = attrs.get("value")
1319- self.disabled = attrs.has_key("disabled")
1320- self.readonly = attrs.has_key("readonly")
1321- self.id = attrs.get("id")
1322-
1323- self.attrs = attrs.copy()
1324-
1325- self._clicked = False
1326-
1327- self._urlparse = urlparse.urlparse
1328- self._urlunparse = urlparse.urlunparse
1329-
1330- def __getattr__(self, name):
1331- if name == "value":
1332- return self.__dict__["_value"]
1333- else:
1334- raise AttributeError("%s instance has no attribute '%s'" %
1335- (self.__class__.__name__, name))
1336-
1337- def __setattr__(self, name, value):
1338- if name == "value":
1339- if not isstringlike(value):
1340- raise TypeError("must assign a string")
1341- elif self.readonly:
1342- raise AttributeError("control '%s' is readonly" % self.name)
1343- elif self.disabled:
1344- raise AttributeError("control '%s' is disabled" % self.name)
1345- self.__dict__["_value"] = value
1346- elif name in ("name", "type"):
1347- raise AttributeError("%s attribute is readonly" % name)
1348- else:
1349- self.__dict__[name] = value
1350-
1351- def _totally_ordered_pairs(self):
1352- name = self.name
1353- value = self.value
1354- if name is None or value is None or self.disabled:
1355- return []
1356- return [(self._index, name, value)]
1357-
1358- def clear(self):
1359- if self.readonly:
1360- raise AttributeError("control '%s' is readonly" % self.name)
1361- self.__dict__["_value"] = None
1362-
1363- def __str__(self):
1364- name = self.name
1365- value = self.value
1366- if name is None: name = "<None>"
1367- if value is None: value = "<None>"
1368-
1369- infos = []
1370- if self.disabled: infos.append("disabled")
1371- if self.readonly: infos.append("readonly")
1372- info = ", ".join(infos)
1373- if info: info = " (%s)" % info
1374-
1375- return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
1376-
1377-
1378-#---------------------------------------------------
1379-class TextControl(ScalarControl):
1380- """Textual input control.
1381-
1382- Covers:
1383-
1384- INPUT/TEXT
1385- INPUT/PASSWORD
1386- INPUT/HIDDEN
1387- TEXTAREA
1388-
1389- """
1390- def __init__(self, type, name, attrs, index=None):
1391- ScalarControl.__init__(self, type, name, attrs, index)
1392- if self.type == "hidden": self.readonly = True
1393- if self._value is None:
1394- self._value = ""
1395-
1396- def is_of_kind(self, kind): return kind == "text"
1397-
1398-#---------------------------------------------------
1399-class FileControl(ScalarControl):
1400- """File upload with INPUT TYPE=FILE.
1401-
1402- The value attribute of a FileControl is always None. Use add_file instead.
1403-
1404- Additional public method: add_file
1405-
1406- """
1407-
1408- def __init__(self, type, name, attrs, index=None):
1409- ScalarControl.__init__(self, type, name, attrs, index)
1410- self._value = None
1411- self._upload_data = []
1412-
1413- def is_of_kind(self, kind): return kind == "file"
1414-
1415- def clear(self):
1416- if self.readonly:
1417- raise AttributeError("control '%s' is readonly" % self.name)
1418- self._upload_data = []
1419-
1420- def __setattr__(self, name, value):
1421- if name in ("value", "name", "type"):
1422- raise AttributeError("%s attribute is readonly" % name)
1423- else:
1424- self.__dict__[name] = value
1425-
1426- def add_file(self, file_object, content_type=None, filename=None):
1427- if not hasattr(file_object, "read"):
1428- raise TypeError("file-like object must have read method")
1429- if content_type is not None and not isstringlike(content_type):
1430- raise TypeError("content type must be None or string-like")
1431- if filename is not None and not isstringlike(filename):
1432- raise TypeError("filename must be None or string-like")
1433- if content_type is None:
1434- content_type = "application/octet-stream"
1435- self._upload_data.append((file_object, content_type, filename))
1436-
1437- def _totally_ordered_pairs(self):
1438- # XXX should it be successful even if unnamed?
1439- if self.name is None or self.disabled:
1440- return []
1441- return [(self._index, self.name, "")]
1442-
1443- def _write_mime_data(self, mw, _name, _value):
1444- # called by HTMLForm
1445- # assert _name == self.name and _value == ''
1446- if len(self._upload_data) < 2:
1447- if len(self._upload_data) == 0:
1448- file_object = StringIO()
1449- content_type = "application/octet-stream"
1450- filename = ""
1451- else:
1452- file_object, content_type, filename = self._upload_data[0]
1453- if filename is None:
1454- filename = ""
1455- mw2 = mw.nextpart()
1456- fn_part = '; filename="%s"' % filename
1457- disp = 'form-data; name="%s"%s' % (self.name, fn_part)
1458- mw2.addheader("Content-Disposition", disp, prefix=1)
1459- fh = mw2.startbody(content_type, prefix=0)
1460- fh.write(file_object.read())
1461- else:
1462- # multiple files
1463- mw2 = mw.nextpart()
1464- disp = 'form-data; name="%s"' % self.name
1465- mw2.addheader("Content-Disposition", disp, prefix=1)
1466- fh = mw2.startmultipartbody("mixed", prefix=0)
1467- for file_object, content_type, filename in self._upload_data:
1468- mw3 = mw2.nextpart()
1469- if filename is None:
1470- filename = ""
1471- fn_part = '; filename="%s"' % filename
1472- disp = "file%s" % fn_part
1473- mw3.addheader("Content-Disposition", disp, prefix=1)
1474- fh2 = mw3.startbody(content_type, prefix=0)
1475- fh2.write(file_object.read())
1476- mw2.lastpart()
1477-
1478- def __str__(self):
1479- name = self.name
1480- if name is None: name = "<None>"
1481-
1482- if not self._upload_data:
1483- value = "<No files added>"
1484- else:
1485- value = []
1486- for file, ctype, filename in self._upload_data:
1487- if filename is None:
1488- value.append("<Unnamed file>")
1489- else:
1490- value.append(filename)
1491- value = ", ".join(value)
1492-
1493- info = []
1494- if self.disabled: info.append("disabled")
1495- if self.readonly: info.append("readonly")
1496- info = ", ".join(info)
1497- if info: info = " (%s)" % info
1498-
1499- return "<%s(%s=%s)%s>" % (self.__class__.__name__, name, value, info)
1500-
1501-
1502-#---------------------------------------------------
1503-class IsindexControl(ScalarControl):
1504- """ISINDEX control.
1505-
1506- ISINDEX is the odd-one-out of HTML form controls. In fact, it isn't really
1507- part of regular HTML forms at all, and predates it. You're only allowed
1508- one ISINDEX per HTML document. ISINDEX and regular form submission are
1509- mutually exclusive -- either submit a form, or the ISINDEX.
1510-
1511- Having said this, since ISINDEX controls may appear in forms (which is
1512- probably bad HTML), ParseFile / ParseResponse will include them in the
1513- HTMLForm instances it returns. You can set the ISINDEX's value, as with
1514- any other control (but note that ISINDEX controls have no name, so you'll
1515- need to use the type argument of set_value!). When you submit the form,
1516- the ISINDEX will not be successful (ie., no data will get returned to the
1517- server as a result of its presence), unless you click on the ISINDEX
1518- control, in which case the ISINDEX gets submitted instead of the form:
1519-
1520- form.set_value("my isindex value", type="isindex")
1521- urllib2.urlopen(form.click(type="isindex"))
1522-
1523- ISINDEX elements outside of FORMs are ignored. If you want to submit one
1524- by hand, do it like so:
1525-
1526- url = urlparse.urljoin(page_uri, "?"+urllib.quote_plus("my isindex value"))
1527- result = urllib2.urlopen(url)
1528-
1529- """
1530- def __init__(self, type, name, attrs, index=None):
1531- ScalarControl.__init__(self, type, name, attrs, index)
1532- if self._value is None:
1533- self._value = ""
1534-
1535- def is_of_kind(self, kind): return kind in ["text", "clickable"]
1536-
1537- def _totally_ordered_pairs(self):
1538- return []
1539-
1540- def _click(self, form, coord, return_type, request_class=urllib2.Request):
1541- # Relative URL for ISINDEX submission: instead of "foo=bar+baz",
1542- # want "bar+baz".
1543- # This doesn't seem to be specified in HTML 4.01 spec. (ISINDEX is
1544- # deprecated in 4.01, but it should still say how to submit it).
1545- # Submission of ISINDEX is explained in the HTML 3.2 spec, though.
1546- parts = self._urlparse(form.action)
1547- rest, (query, frag) = parts[:-2], parts[-2:]
1548- parts = rest + (urllib.quote_plus(self.value), None)
1549- url = self._urlunparse(parts)
1550- req_data = url, None, []
1551-
1552- if return_type == "pairs":
1553- return []
1554- elif return_type == "request_data":
1555- return req_data
1556- else:
1557- return request_class(url)
1558-
1559- def __str__(self):
1560- value = self.value
1561- if value is None: value = "<None>"
1562-
1563- infos = []
1564- if self.disabled: infos.append("disabled")
1565- if self.readonly: infos.append("readonly")
1566- info = ", ".join(infos)
1567- if info: info = " (%s)" % info
1568-
1569- return "<%s(%s)%s>" % (self.__class__.__name__, value, info)
1570-
1571-
1572-#---------------------------------------------------
1573-class IgnoreControl(ScalarControl):
1574- """Control that we're not interested in.
1575-
1576- Covers:
1577-
1578- INPUT/RESET
1579- BUTTON/RESET
1580- INPUT/BUTTON
1581- BUTTON/BUTTON
1582-
1583- These controls are always unsuccessful, in the terminology of HTML 4 (ie.
1584- they never require any information to be returned to the server).
1585-
1586- BUTTON/BUTTON is used to generate events for script embedded in HTML.
1587-
1588- The value attribute of IgnoreControl is always None.
1589-
1590- """
1591- def __init__(self, type, name, attrs, index=None):
1592- ScalarControl.__init__(self, type, name, attrs, index)
1593- self._value = None
1594-
1595- def is_of_kind(self, kind): return False
1596-
1597- def __setattr__(self, name, value):
1598- if name == "value":
1599- raise AttributeError(
1600- "control '%s' is ignored, hence read-only" % self.name)
1601- elif name in ("name", "type"):
1602- raise AttributeError("%s attribute is readonly" % name)
1603- else:
1604- self.__dict__[name] = value
1605-
1606-
1607-#---------------------------------------------------
1608-# ListControls
1609-
1610-# helpers and subsidiary classes
1611-
1612-class Item:
1613- def __init__(self, control, attrs, index=None):
1614- label = _get_label(attrs)
1615- self.__dict__.update({
1616- "name": attrs["value"],
1617- "_labels": label and [label] or [],
1618- "attrs": attrs,
1619- "_control": control,
1620- "disabled": attrs.has_key("disabled"),
1621- "_selected": False,
1622- "id": attrs.get("id"),
1623- "_index": index,
1624- })
1625- control.items.append(self)
1626-
1627- def get_labels(self):
1628- """Return all labels (Label instances) for this item.
1629-
1630- For items that represent radio buttons or checkboxes, if the item was
1631- surrounded by a <label> tag, that will be the first label; all other
1632- labels, connected by 'for' and 'id', are in the order that appear in
1633- the HTML.
1634-
1635- For items that represent select options, if the option had a label
1636- attribute, that will be the first label. If the option has contents
1637- (text within the option tags) and it is not the same as the label
1638- attribute (if any), that will be a label. There is nothing in the
1639- spec to my knowledge that makes an option with an id unable to be the
1640- target of a label's for attribute, so those are included, if any, for
1641- the sake of consistency and completeness.
1642-
1643- """
1644- res = []
1645- res.extend(self._labels)
1646- if self.id:
1647- res.extend(self._control._form._id_to_labels.get(self.id, ()))
1648- return res
1649-
1650- def __getattr__(self, name):
1651- if name=="selected":
1652- return self._selected
1653- raise AttributeError(name)
1654-
1655- def __setattr__(self, name, value):
1656- if name == "selected":
1657- self._control._set_selected_state(self, value)
1658- elif name == "disabled":
1659- self.__dict__["disabled"] = bool(value)
1660- else:
1661- raise AttributeError(name)
1662-
1663- def __str__(self):
1664- res = self.name
1665- if self.selected:
1666- res = "*" + res
1667- if self.disabled:
1668- res = "(%s)" % res
1669- return res
1670-
1671- def __repr__(self):
1672- # XXX appending the attrs without distinguishing them from name and id
1673- # is silly
1674- attrs = [("name", self.name), ("id", self.id)]+self.attrs.items()
1675- return "<%s %s>" % (
1676- self.__class__.__name__,
1677- " ".join(["%s=%r" % (k, v) for k, v in attrs])
1678- )
1679-
1680-def disambiguate(items, nr, **kwds):
1681- msgs = []
1682- for key, value in kwds.items():
1683- msgs.append("%s=%r" % (key, value))
1684- msg = " ".join(msgs)
1685- if not items:
1686- raise ItemNotFoundError(msg)
1687- if nr is None:
1688- if len(items) > 1:
1689- raise AmbiguityError(msg)
1690- nr = 0
1691- if len(items) <= nr:
1692- raise ItemNotFoundError(msg)
1693- return items[nr]
1694-
1695-class ListControl(Control):
1696- """Control representing a sequence of items.
1697-
1698- The value attribute of a ListControl represents the successful list items
1699- in the control. The successful list items are those that are selected and
1700- not disabled.
1701-
1702- ListControl implements both list controls that take a length-1 value
1703- (single-selection) and those that take length >1 values
1704- (multiple-selection).
1705-
1706- ListControls accept sequence values only. Some controls only accept
1707- sequences of length 0 or 1 (RADIO, and single-selection SELECT).
1708- In those cases, ItemCountError is raised if len(sequence) > 1. CHECKBOXes
1709- and multiple-selection SELECTs (those having the "multiple" HTML attribute)
1710- accept sequences of any length.
1711-
1712- Note the following mistake:
1713-
1714- control.value = some_value
1715- assert control.value == some_value # not necessarily true
1716-
1717- The reason for this is that the value attribute always gives the list items
1718- in the order they were listed in the HTML.
1719-
1720- ListControl items can also be referred to by their labels instead of names.
1721- Use the label argument to .get(), and the .set_value_by_label(),
1722- .get_value_by_label() methods.
1723-
1724- Note that, rather confusingly, though SELECT controls are represented in
1725- HTML by SELECT elements (which contain OPTION elements, representing
1726- individual list items), CHECKBOXes and RADIOs are not represented by *any*
1727- element. Instead, those controls are represented by a collection of INPUT
1728- elements. For example, this is a SELECT control, named "control1":
1729-
1730- <select name="control1">
1731- <option>foo</option>
1732- <option value="1">bar</option>
1733- </select>
1734-
1735- and this is a CHECKBOX control, named "control2":
1736-
1737- <input type="checkbox" name="control2" value="foo" id="cbe1">
1738- <input type="checkbox" name="control2" value="bar" id="cbe2">
1739-
1740- The id attribute of a CHECKBOX or RADIO ListControl is always that of its
1741- first element (for example, "cbe1" above).
1742-
1743-
1744- Additional read-only public attribute: multiple.
1745-
1746- """
1747-
1748- # ListControls are built up by the parser from their component items by
1749- # creating one ListControl per item, consolidating them into a single
1750- # master ListControl held by the HTMLForm:
1751-
1752- # -User calls form.new_control(...)
1753- # -Form creates Control, and calls control.add_to_form(self).
1754- # -Control looks for a Control with the same name and type in the form,
1755- # and if it finds one, merges itself with that control by calling
1756- # control.merge_control(self). The first Control added to the form, of
1757- # a particular name and type, is the only one that survives in the
1758- # form.
1759- # -Form calls control.fixup for all its controls. ListControls in the
1760- # form know they can now safely pick their default values.
1761-
1762- # To create a ListControl without an HTMLForm, use:
1763-
1764- # control.merge_control(new_control)
1765-
1766- # (actually, it's much easier just to use ParseFile)
1767-
1768- _label = None
1769-
1770- def __init__(self, type, name, attrs={}, select_default=False,
1771- called_as_base_class=False, index=None):
1772- """
1773- select_default: for RADIO and multiple-selection SELECT controls, pick
1774- the first item as the default if no 'selected' HTML attribute is
1775- present
1776-
1777- """
1778- if not called_as_base_class:
1779- raise NotImplementedError()
1780-
1781- self.__dict__["type"] = type.lower()
1782- self.__dict__["name"] = name
1783- self._value = attrs.get("value")
1784- self.disabled = False
1785- self.readonly = False
1786- self.id = attrs.get("id")
1787- self._closed = False
1788-
1789- # As Controls are merged in with .merge_control(), self.attrs will
1790- # refer to each Control in turn -- always the most recently merged
1791- # control. Each merged-in Control instance corresponds to a single
1792- # list item: see ListControl.__doc__.
1793- self.items = []
1794- self._form = None
1795-
1796- self._select_default = select_default
1797- self._clicked = False
1798-
1799- def clear(self):
1800- self.value = []
1801-
1802- def is_of_kind(self, kind):
1803- if kind == "list":
1804- return True
1805- elif kind == "multilist":
1806- return bool(self.multiple)
1807- elif kind == "singlelist":
1808- return not self.multiple
1809- else:
1810- return False
1811-
1812- def get_items(self, name=None, label=None, id=None,
1813- exclude_disabled=False):
1814- """Return matching items by name or label.
1815-
1816- For argument docs, see the docstring for .get()
1817-
1818- """
1819- if name is not None and not isstringlike(name):
1820- raise TypeError("item name must be string-like")
1821- if label is not None and not isstringlike(label):
1822- raise TypeError("item label must be string-like")
1823- if id is not None and not isstringlike(id):
1824- raise TypeError("item id must be string-like")
1825- items = [] # order is important
1826- compat = self._form.backwards_compat
1827- for o in self.items:
1828- if exclude_disabled and o.disabled:
1829- continue
1830- if name is not None and o.name != name:
1831- continue
1832- if label is not None:
1833- for l in o.get_labels():
1834- if ((compat and l.text == label) or
1835- (not compat and l.text.find(label) > -1)):
1836- break
1837- else:
1838- continue
1839- if id is not None and o.id != id:
1840- continue
1841- items.append(o)
1842- return items
1843-
1844- def get(self, name=None, label=None, id=None, nr=None,
1845- exclude_disabled=False):
1846- """Return item by name or label, disambiguating if necessary with nr.
1847-
1848- All arguments must be passed by name, with the exception of 'name',
1849- which may be used as a positional argument.
1850-
1851- If name is specified, then the item must have the indicated name.
1852-
1853- If label is specified, then the item must have a label whose
1854- whitespace-compressed, stripped, text substring-matches the indicated
1855- label string (eg. label="please choose" will match
1856- " Do please choose an item ").
1857-
1858- If id is specified, then the item must have the indicated id.
1859-
1860- nr is an optional 0-based index of the items matching the query.
1861-
1862- If nr is the default None value and more than item is found, raises
1863- AmbiguityError (unless the HTMLForm instance's backwards_compat
1864- attribute is true).
1865-
1866- If no item is found, or if items are found but nr is specified and not
1867- found, raises ItemNotFoundError.
1868-
1869- Optionally excludes disabled items.
1870-
1871- """
1872- if nr is None and self._form.backwards_compat:
1873- nr = 0 # :-/
1874- items = self.get_items(name, label, id, exclude_disabled)
1875- return disambiguate(items, nr, name=name, label=label, id=id)
1876-
1877- def _get(self, name, by_label=False, nr=None, exclude_disabled=False):
1878- # strictly for use by deprecated methods
1879- if by_label:
1880- name, label = None, name
1881- else:
1882- name, label = name, None
1883- return self.get(name, label, nr, exclude_disabled)
1884-
1885- def toggle(self, name, by_label=False, nr=None):
1886- """Deprecated: given a name or label and optional disambiguating index
1887- nr, toggle the matching item's selection.
1888-
1889- Selecting items follows the behavior described in the docstring of the
1890- 'get' method.
1891-
1892- if the item is disabled, or this control is disabled or readonly,
1893- raise AttributeError.
1894-
1895- """
1896- deprecation(
1897- "item = control.get(...); item.selected = not item.selected")
1898- o = self._get(name, by_label, nr)
1899- self._set_selected_state(o, not o.selected)
1900-
1901- def set(self, selected, name, by_label=False, nr=None):
1902- """Deprecated: given a name or label and optional disambiguating index
1903- nr, set the matching item's selection to the bool value of selected.
1904-
1905- Selecting items follows the behavior described in the docstring of the
1906- 'get' method.
1907-
1908- if the item is disabled, or this control is disabled or readonly,
1909- raise AttributeError.
1910-
1911- """
1912- deprecation(
1913- "control.get(...).selected = <boolean>")
1914- self._set_selected_state(self._get(name, by_label, nr), selected)
1915-
1916- def _set_selected_state(self, item, action):
1917- # action:
1918- # bool False: off
1919- # bool True: on
1920- if self.disabled:
1921- raise AttributeError("control '%s' is disabled" % self.name)
1922- if self.readonly:
1923- raise AttributeError("control '%s' is readonly" % self.name)
1924- action == bool(action)
1925- compat = self._form.backwards_compat
1926- if not compat and item.disabled:
1927- raise AttributeError("item is disabled")
1928- else:
1929- if compat and item.disabled and action:
1930- raise AttributeError("item is disabled")
1931- if self.multiple:
1932- item.__dict__["_selected"] = action
1933- else:
1934- if not action:
1935- item.__dict__["_selected"] = False
1936- else:
1937- for o in self.items:
1938- o.__dict__["_selected"] = False
1939- item.__dict__["_selected"] = True
1940-
1941- def toggle_single(self, by_label=None):
1942- """Deprecated: toggle the selection of the single item in this control.
1943-
1944- Raises ItemCountError if the control does not contain only one item.
1945-
1946- by_label argument is ignored, and included only for backwards
1947- compatibility.
1948-
1949- """
1950- deprecation(
1951- "control.items[0].selected = not control.items[0].selected")
1952- if len(self.items) != 1:
1953- raise ItemCountError(
1954- "'%s' is not a single-item control" % self.name)
1955- item = self.items[0]
1956- self._set_selected_state(item, not item.selected)
1957-
1958- def set_single(self, selected, by_label=None):
1959- """Deprecated: set the selection of the single item in this control.
1960-
1961- Raises ItemCountError if the control does not contain only one item.
1962-
1963- by_label argument is ignored, and included only for backwards
1964- compatibility.
1965-
1966- """
1967- deprecation(
1968- "control.items[0].selected = <boolean>")
1969- if len(self.items) != 1:
1970- raise ItemCountError(
1971- "'%s' is not a single-item control" % self.name)
1972- self._set_selected_state(self.items[0], selected)
1973-
1974- def get_item_disabled(self, name, by_label=False, nr=None):
1975- """Get disabled state of named list item in a ListControl."""
1976- deprecation(
1977- "control.get(...).disabled")
1978- return self._get(name, by_label, nr).disabled
1979-
1980- def set_item_disabled(self, disabled, name, by_label=False, nr=None):
1981- """Set disabled state of named list item in a ListControl.
1982-
1983- disabled: boolean disabled state
1984-
1985- """
1986- deprecation(
1987- "control.get(...).disabled = <boolean>")
1988- self._get(name, by_label, nr).disabled = disabled
1989-
1990- def set_all_items_disabled(self, disabled):
1991- """Set disabled state of all list items in a ListControl.
1992-
1993- disabled: boolean disabled state
1994-
1995- """
1996- for o in self.items:
1997- o.disabled = disabled
1998-
1999- def get_item_attrs(self, name, by_label=False, nr=None):
2000- """Return dictionary of HTML attributes for a single ListControl item.
2001-
2002- The HTML element types that describe list items are: OPTION for SELECT
2003- controls, INPUT for the rest. These elements have HTML attributes that
2004- you may occasionally want to know about -- for example, the "alt" HTML
2005- attribute gives a text string describing the item (graphical browsers
2006- usually display this as a tooltip).
2007-
2008- The returned dictionary maps HTML attribute names to values. The names
2009- and values are taken from the original HTML.
2010-
2011- """
2012- deprecation(
2013- "control.get(...).attrs")
2014- return self._get(name, by_label, nr).attrs
2015-
2016- def close_control(self):
2017- self._closed = True
2018-
2019- def add_to_form(self, form):
2020- assert self._form is None or form == self._form, (
2021- "can't add control to more than one form")
2022- self._form = form
2023- if self.name is None:
2024- # always count nameless elements as separate controls
2025- Control.add_to_form(self, form)
2026- else:
2027- for ii in range(len(form.controls)-1, -1, -1):
2028- control = form.controls[ii]
2029- if control.name == self.name and control.type == self.type:
2030- if control._closed:
2031- Control.add_to_form(self, form)
2032- else:
2033- control.merge_control(self)
2034- break
2035- else:
2036- Control.add_to_form(self, form)
2037-
2038- def merge_control(self, control):
2039- assert bool(control.multiple) == bool(self.multiple)
2040- # usually, isinstance(control, self.__class__)
2041- self.items.extend(control.items)
2042-
2043- def fixup(self):
2044- """
2045- ListControls are built up from component list items (which are also
2046- ListControls) during parsing. This method should be called after all
2047- items have been added. See ListControl.__doc__ for the reason this is
2048- required.
2049-
2050- """
2051- # Need to set default selection where no item was indicated as being
2052- # selected by the HTML:
2053-
2054- # CHECKBOX:
2055- # Nothing should be selected.
2056- # SELECT/single, SELECT/multiple and RADIO:
2057- # RFC 1866 (HTML 2.0): says first item should be selected.
2058- # W3C HTML 4.01 Specification: says that client behaviour is
2059- # undefined in this case. For RADIO, exactly one must be selected,
2060- # though which one is undefined.
2061- # Both Netscape and Microsoft Internet Explorer (IE) choose first
2062- # item for SELECT/single. However, both IE5 and Mozilla (both 1.0
2063- # and Firebird 0.6) leave all items unselected for RADIO and
2064- # SELECT/multiple.
2065-
2066- # Since both Netscape and IE all choose the first item for
2067- # SELECT/single, we do the same. OTOH, both Netscape and IE
2068- # leave SELECT/multiple with nothing selected, in violation of RFC 1866
2069- # (but not in violation of the W3C HTML 4 standard); the same is true
2070- # of RADIO (which *is* in violation of the HTML 4 standard). We follow
2071- # RFC 1866 if the _select_default attribute is set, and Netscape and IE
2072- # otherwise. RFC 1866 and HTML 4 are always violated insofar as you
2073- # can deselect all items in a RadioControl.
2074-
2075- for o in self.items:
2076- # set items' controls to self, now that we've merged
2077- o.__dict__["_control"] = self
2078-
2079- def __getattr__(self, name):
2080- if name == "value":
2081- compat = self._form.backwards_compat
2082- if self.name is None:
2083- return []
2084- return [o.name for o in self.items if o.selected and
2085- (not o.disabled or compat)]
2086- else:
2087- raise AttributeError("%s instance has no attribute '%s'" %
2088- (self.__class__.__name__, name))
2089-
2090- def __setattr__(self, name, value):
2091- if name == "value":
2092- if self.disabled:
2093- raise AttributeError("control '%s' is disabled" % self.name)
2094- if self.readonly:
2095- raise AttributeError("control '%s' is readonly" % self.name)
2096- self._set_value(value)
2097- elif name in ("name", "type", "multiple"):
2098- raise AttributeError("%s attribute is readonly" % name)
2099- else:
2100- self.__dict__[name] = value
2101-
2102- def _set_value(self, value):
2103- if value is None or isstringlike(value):
2104- raise TypeError("ListControl, must set a sequence")
2105- if not value:
2106- compat = self._form.backwards_compat
2107- for o in self.items:
2108- if not o.disabled or compat:
2109- o.selected = False
2110- elif self.multiple:
2111- self._multiple_set_value(value)
2112- elif len(value) > 1:
2113- raise ItemCountError(
2114- "single selection list, must set sequence of "
2115- "length 0 or 1")
2116- else:
2117- self._single_set_value(value)
2118-
2119- def _get_items(self, name, target=1):
2120- all_items = self.get_items(name)
2121- items = [o for o in all_items if not o.disabled]
2122- if len(items) < target:
2123- if len(all_items) < target:
2124- raise ItemNotFoundError(
2125- "insufficient items with name %r" % name)
2126- else:
2127- raise AttributeError(
2128- "insufficient non-disabled items with name %s" % name)
2129- on = []
2130- off = []
2131- for o in items:
2132- if o.selected:
2133- on.append(o)
2134- else:
2135- off.append(o)
2136- return on, off
2137-
2138- def _single_set_value(self, value):
2139- assert len(value) == 1
2140- on, off = self._get_items(value[0])
2141- assert len(on) <= 1
2142- if not on:
2143- off[0].selected = True
2144-
2145- def _multiple_set_value(self, value):
2146- compat = self._form.backwards_compat
2147- turn_on = [] # transactional-ish
2148- turn_off = [item for item in self.items if
2149- item.selected and (not item.disabled or compat)]
2150- names = {}
2151- for nn in value:
2152- if nn in names.keys():
2153- names[nn] += 1
2154- else:
2155- names[nn] = 1
2156- for name, count in names.items():
2157- on, off = self._get_items(name, count)
2158- for i in range(count):
2159- if on:
2160- item = on[0]
2161- del on[0]
2162- del turn_off[turn_off.index(item)]
2163- else:
2164- item = off[0]
2165- del off[0]
2166- turn_on.append(item)
2167- for item in turn_off:
2168- item.selected = False
2169- for item in turn_on:
2170- item.selected = True
2171-
2172- def set_value_by_label(self, value):
2173- """Set the value of control by item labels.
2174-
2175- value is expected to be an iterable of strings that are substrings of
2176- the item labels that should be selected. Before substring matching is
2177- performed, the original label text is whitespace-compressed
2178- (consecutive whitespace characters are converted to a single space
2179- character) and leading and trailing whitespace is stripped. Ambiguous
2180- labels are accepted without complaint if the form's backwards_compat is
2181- True; otherwise, it will not complain as long as all ambiguous labels
2182- share the same item name (e.g. OPTION value).
2183-
2184- """
2185- if isstringlike(value):
2186- raise TypeError(value)
2187- if not self.multiple and len(value) > 1:
2188- raise ItemCountError(
2189- "single selection list, must set sequence of "
2190- "length 0 or 1")
2191- items = []
2192- for nn in value:
2193- found = self.get_items(label=nn)
2194- if len(found) > 1:
2195- if not self._form.backwards_compat:
2196- # ambiguous labels are fine as long as item names (e.g.
2197- # OPTION values) are same
2198- opt_name = found[0].name
2199- if [o for o in found[1:] if o.name != opt_name]:
2200- raise AmbiguityError(nn)
2201- else:
2202- # OK, we'll guess :-( Assume first available item.
2203- found = found[:1]
2204- for o in found:
2205- # For the multiple-item case, we could try to be smarter,
2206- # saving them up and trying to resolve, but that's too much.
2207- if self._form.backwards_compat or o not in items:
2208- items.append(o)
2209- break
2210- else: # all of them are used
2211- raise ItemNotFoundError(nn)
2212- # now we have all the items that should be on
2213- # let's just turn everything off and then back on.
2214- self.value = []
2215- for o in items:
2216- o.selected = True
2217-
2218- def get_value_by_label(self):
2219- """Return the value of the control as given by normalized labels."""
2220- res = []
2221- compat = self._form.backwards_compat
2222- for o in self.items:
2223- if (not o.disabled or compat) and o.selected:
2224- for l in o.get_labels():
2225- if l.text:
2226- res.append(l.text)
2227- break
2228- else:
2229- res.append(None)
2230- return res
2231-
2232- def possible_items(self, by_label=False):
2233- """Deprecated: return the names or labels of all possible items.
2234-
2235- Includes disabled items, which may be misleading for some use cases.
2236-
2237- """
2238- deprecation(
2239- "[item.name for item in self.items]")
2240- if by_label:
2241- res = []
2242- for o in self.items:
2243- for l in o.get_labels():
2244- if l.text:
2245- res.append(l.text)
2246- break
2247- else:
2248- res.append(None)
2249- return res
2250- return [o.name for o in self.items]
2251-
2252- def _totally_ordered_pairs(self):
2253- if self.disabled or self.name is None:
2254- return []
2255- else:
2256- return [(o._index, self.name, o.name) for o in self.items
2257- if o.selected and not o.disabled]
2258-
2259- def __str__(self):
2260- name = self.name
2261- if name is None: name = "<None>"
2262-
2263- display = [str(o) for o in self.items]
2264-
2265- infos = []
2266- if self.disabled: infos.append("disabled")
2267- if self.readonly: infos.append("readonly")
2268- info = ", ".join(infos)
2269- if info: info = " (%s)" % info
2270-
2271- return "<%s(%s=[%s])%s>" % (self.__class__.__name__,
2272- name, ", ".join(display), info)
2273-
2274-
2275-class RadioControl(ListControl):
2276- """
2277- Covers:
2278-
2279- INPUT/RADIO
2280-
2281- """
2282- def __init__(self, type, name, attrs, select_default=False, index=None):
2283- attrs.setdefault("value", "on")
2284- ListControl.__init__(self, type, name, attrs, select_default,
2285- called_as_base_class=True, index=index)
2286- self.__dict__["multiple"] = False
2287- o = Item(self, attrs, index)
2288- o.__dict__["_selected"] = attrs.has_key("checked")
2289-
2290- def fixup(self):
2291- ListControl.fixup(self)
2292- found = [o for o in self.items if o.selected and not o.disabled]
2293- if not found:
2294- if self._select_default:
2295- for o in self.items:
2296- if not o.disabled:
2297- o.selected = True
2298- break
2299- else:
2300- # Ensure only one item selected. Choose the last one,
2301- # following IE and Firefox.
2302- for o in found[:-1]:
2303- o.selected = False
2304-
2305- def get_labels(self):
2306- return []
2307-
2308-class CheckboxControl(ListControl):
2309- """
2310- Covers:
2311-
2312- INPUT/CHECKBOX
2313-
2314- """
2315- def __init__(self, type, name, attrs, select_default=False, index=None):
2316- attrs.setdefault("value", "on")
2317- ListControl.__init__(self, type, name, attrs, select_default,
2318- called_as_base_class=True, index=index)
2319- self.__dict__["multiple"] = True
2320- o = Item(self, attrs, index)
2321- o.__dict__["_selected"] = attrs.has_key("checked")
2322-
2323- def get_labels(self):
2324- return []
2325-
2326-
2327-class SelectControl(ListControl):
2328- """
2329- Covers:
2330-
2331- SELECT (and OPTION)
2332-
2333-
2334- OPTION 'values', in HTML parlance, are Item 'names' in ClientForm parlance.
2335-
2336- SELECT control values and labels are subject to some messy defaulting
2337- rules. For example, if the HTML representation of the control is:
2338-
2339- <SELECT name=year>
2340- <OPTION value=0 label="2002">current year</OPTION>
2341- <OPTION value=1>2001</OPTION>
2342- <OPTION>2000</OPTION>
2343- </SELECT>
2344-
2345- The items, in order, have labels "2002", "2001" and "2000", whereas their
2346- names (the OPTION values) are "0", "1" and "2000" respectively. Note that
2347- the value of the last OPTION in this example defaults to its contents, as
2348- specified by RFC 1866, as do the labels of the second and third OPTIONs.
2349-
2350- The OPTION labels are sometimes more meaningful than the OPTION values,
2351- which can make for more maintainable code.
2352-
2353- Additional read-only public attribute: attrs
2354-
2355- The attrs attribute is a dictionary of the original HTML attributes of the
2356- SELECT element. Other ListControls do not have this attribute, because in
2357- other cases the control as a whole does not correspond to any single HTML
2358- element. control.get(...).attrs may be used as usual to get at the HTML
2359- attributes of the HTML elements corresponding to individual list items (for
2360- SELECT controls, these are OPTION elements).
2361-
2362- Another special case is that the Item.attrs dictionaries have a special key
2363- "contents" which does not correspond to any real HTML attribute, but rather
2364- contains the contents of the OPTION element:
2365-
2366- <OPTION>this bit</OPTION>
2367-
2368- """
2369- # HTML attributes here are treated slightly differently from other list
2370- # controls:
2371- # -The SELECT HTML attributes dictionary is stuffed into the OPTION
2372- # HTML attributes dictionary under the "__select" key.
2373- # -The content of each OPTION element is stored under the special
2374- # "contents" key of the dictionary.
2375- # After all this, the dictionary is passed to the SelectControl constructor
2376- # as the attrs argument, as usual. However:
2377- # -The first SelectControl constructed when building up a SELECT control
2378- # has a constructor attrs argument containing only the __select key -- so
2379- # this SelectControl represents an empty SELECT control.
2380- # -Subsequent SelectControls have both OPTION HTML-attribute in attrs and
2381- # the __select dictionary containing the SELECT HTML-attributes.
2382-
2383- def __init__(self, type, name, attrs, select_default=False, index=None):
2384- # fish out the SELECT HTML attributes from the OPTION HTML attributes
2385- # dictionary
2386- self.attrs = attrs["__select"].copy()
2387- self.__dict__["_label"] = _get_label(self.attrs)
2388- self.__dict__["id"] = self.attrs.get("id")
2389- self.__dict__["multiple"] = self.attrs.has_key("multiple")
2390- # the majority of the contents, label, and value dance already happened
2391- contents = attrs.get("contents")
2392- attrs = attrs.copy()
2393- del attrs["__select"]
2394-
2395- ListControl.__init__(self, type, name, self.attrs, select_default,
2396- called_as_base_class=True, index=index)
2397- self.disabled = self.attrs.has_key("disabled")
2398- self.readonly = self.attrs.has_key("readonly")
2399- if attrs.has_key("value"):
2400- # otherwise it is a marker 'select started' token
2401- o = Item(self, attrs, index)
2402- o.__dict__["_selected"] = attrs.has_key("selected")
2403- # add 'label' label and contents label, if different. If both are
2404- # provided, the 'label' label is used for display in HTML
2405- # 4.0-compliant browsers (and any lower spec? not sure) while the
2406- # contents are used for display in older or less-compliant
2407- # browsers. We make label objects for both, if the values are
2408- # different.
2409- label = attrs.get("label")
2410- if label:
2411- o._labels.append(Label({"__text": label}))
2412- if contents and contents != label:
2413- o._labels.append(Label({"__text": contents}))
2414- elif contents:
2415- o._labels.append(Label({"__text": contents}))
2416-
2417- def fixup(self):
2418- ListControl.fixup(self)
2419- # Firefox doesn't exclude disabled items from those considered here
2420- # (i.e. from 'found', for both branches of the if below). Note that
2421- # IE6 doesn't support the disabled attribute on OPTIONs at all.
2422- found = [o for o in self.items if o.selected]
2423- if not found:
2424- if not self.multiple or self._select_default:
2425- for o in self.items:
2426- if not o.disabled:
2427- was_disabled = self.disabled
2428- self.disabled = False
2429- try:
2430- o.selected = True
2431- finally:
2432- o.disabled = was_disabled
2433- break
2434- elif not self.multiple:
2435- # Ensure only one item selected. Choose the last one,
2436- # following IE and Firefox.
2437- for o in found[:-1]:
2438- o.selected = False
2439-
2440-
2441-#---------------------------------------------------
2442-class SubmitControl(ScalarControl):
2443- """
2444- Covers:
2445-
2446- INPUT/SUBMIT
2447- BUTTON/SUBMIT
2448-
2449- """
2450- def __init__(self, type, name, attrs, index=None):
2451- ScalarControl.__init__(self, type, name, attrs, index)
2452- # IE5 defaults SUBMIT value to "Submit Query"; Firebird 0.6 leaves it
2453- # blank, Konqueror 3.1 defaults to "Submit". HTML spec. doesn't seem
2454- # to define this.
2455- if self.value is None: self.value = ""
2456- self.readonly = True
2457-
2458- def get_labels(self):
2459- res = []
2460- if self.value:
2461- res.append(Label({"__text": self.value}))
2462- res.extend(ScalarControl.get_labels(self))
2463- return res
2464-
2465- def is_of_kind(self, kind): return kind == "clickable"
2466-
2467- def _click(self, form, coord, return_type, request_class=urllib2.Request):
2468- self._clicked = coord
2469- r = form._switch_click(return_type, request_class)
2470- self._clicked = False
2471- return r
2472-
2473- def _totally_ordered_pairs(self):
2474- if not self._clicked:
2475- return []
2476- return ScalarControl._totally_ordered_pairs(self)
2477-
2478-
2479-#---------------------------------------------------
2480-class ImageControl(SubmitControl):
2481- """
2482- Covers:
2483-
2484- INPUT/IMAGE
2485-
2486- Coordinates are specified using one of the HTMLForm.click* methods.
2487-
2488- """
2489- def __init__(self, type, name, attrs, index=None):
2490- SubmitControl.__init__(self, type, name, attrs, index)
2491- self.readonly = False
2492-
2493- def _totally_ordered_pairs(self):
2494- clicked = self._clicked
2495- if self.disabled or not clicked:
2496- return []
2497- name = self.name
2498- if name is None: return []
2499- pairs = [
2500- (self._index, "%s.x" % name, str(clicked[0])),
2501- (self._index+1, "%s.y" % name, str(clicked[1])),
2502- ]
2503- value = self._value
2504- if value:
2505- pairs.append((self._index+2, name, value))
2506- return pairs
2507-
2508- get_labels = ScalarControl.get_labels
2509-
2510-# aliases, just to make str(control) and str(form) clearer
2511-class PasswordControl(TextControl): pass
2512-class HiddenControl(TextControl): pass
2513-class TextareaControl(TextControl): pass
2514-class SubmitButtonControl(SubmitControl): pass
2515-
2516-
2517-def is_listcontrol(control): return control.is_of_kind("list")
2518-
2519-
2520-class HTMLForm:
2521- """Represents a single HTML <form> ... </form> element.
2522-
2523- A form consists of a sequence of controls that usually have names, and
2524- which can take on various values. The values of the various types of
2525- controls represent variously: text, zero-or-one-of-many or many-of-many
2526- choices, and files to be uploaded. Some controls can be clicked on to
2527- submit the form, and clickable controls' values sometimes include the
2528- coordinates of the click.
2529-
2530- Forms can be filled in with data to be returned to the server, and then
2531- submitted, using the click method to generate a request object suitable for
2532- passing to urllib2.urlopen (or the click_request_data or click_pairs
2533- methods if you're not using urllib2).
2534-
2535- import ClientForm
2536- forms = ClientForm.ParseFile(html, base_uri)
2537- form = forms[0]
2538-
2539- form["query"] = "Python"
2540- form.find_control("nr_results").get("lots").selected = True
2541-
2542- response = urllib2.urlopen(form.click())
2543-
2544- Usually, HTMLForm instances are not created directly. Instead, the
2545- ParseFile or ParseResponse factory functions are used. If you do construct
2546- HTMLForm objects yourself, however, note that an HTMLForm instance is only
2547- properly initialised after the fixup method has been called (ParseFile and
2548- ParseResponse do this for you). See ListControl.__doc__ for the reason
2549- this is required.
2550-
2551- Indexing a form (form["control_name"]) returns the named Control's value
2552- attribute. Assignment to a form index (form["control_name"] = something)
2553- is equivalent to assignment to the named Control's value attribute. If you
2554- need to be more specific than just supplying the control's name, use the
2555- set_value and get_value methods.
2556-
2557- ListControl values are lists of item names (specifically, the names of the
2558- items that are selected and not disabled, and hence are "successful" -- ie.
2559- cause data to be returned to the server). The list item's name is the
2560- value of the corresponding HTML element's"value" attribute.
2561-
2562- Example:
2563-
2564- <INPUT type="CHECKBOX" name="cheeses" value="leicester"></INPUT>
2565- <INPUT type="CHECKBOX" name="cheeses" value="cheddar"></INPUT>
2566-
2567- defines a CHECKBOX control with name "cheeses" which has two items, named
2568- "leicester" and "cheddar".
2569-
2570- Another example:
2571-
2572- <SELECT name="more_cheeses">
2573- <OPTION>1</OPTION>
2574- <OPTION value="2" label="CHEDDAR">cheddar</OPTION>
2575- </SELECT>
2576-
2577- defines a SELECT control with name "more_cheeses" which has two items,
2578- named "1" and "2" (because the OPTION element's value HTML attribute
2579- defaults to the element contents -- see SelectControl.__doc__ for more on
2580- these defaulting rules).
2581-
2582- To select, deselect or otherwise manipulate individual list items, use the
2583- HTMLForm.find_control() and ListControl.get() methods. To set the whole
2584- value, do as for any other control: use indexing or the set_/get_value
2585- methods.
2586-
2587- Example:
2588-
2589- # select *only* the item named "cheddar"
2590- form["cheeses"] = ["cheddar"]
2591- # select "cheddar", leave other items unaffected
2592- form.find_control("cheeses").get("cheddar").selected = True
2593-
2594- Some controls (RADIO and SELECT without the multiple attribute) can only
2595- have zero or one items selected at a time. Some controls (CHECKBOX and
2596- SELECT with the multiple attribute) can have multiple items selected at a
2597- time. To set the whole value of a ListControl, assign a sequence to a form
2598- index:
2599-
2600- form["cheeses"] = ["cheddar", "leicester"]
2601-
2602- If the ListControl is not multiple-selection, the assigned list must be of
2603- length one.
2604-
2605- To check if a control has an item, if an item is selected, or if an item is
2606- successful (selected and not disabled), respectively:
2607-
2608- "cheddar" in [item.name for item in form.find_control("cheeses").items]
2609- "cheddar" in [item.name for item in form.find_control("cheeses").items and
2610- item.selected]
2611- "cheddar" in form["cheeses"] # (or "cheddar" in form.get_value("cheeses"))
2612-
2613- Note that some list items may be disabled (see below).
2614-
2615- Note the following mistake:
2616-
2617- form[control_name] = control_value
2618- assert form[control_name] == control_value # not necessarily true
2619-
2620- The reason for this is that form[control_name] always gives the list items
2621- in the order they were listed in the HTML.
2622-
2623- List items (hence list values, too) can be referred to in terms of list
2624- item labels rather than list item names using the appropriate label
2625- arguments. Note that each item may have several labels.
2626-
2627- The question of default values of OPTION contents, labels and values is
2628- somewhat complicated: see SelectControl.__doc__ and
2629- ListControl.get_item_attrs.__doc__ if you think you need to know.
2630-
2631- Controls can be disabled or readonly. In either case, the control's value
2632- cannot be changed until you clear those flags (see example below).
2633- Disabled is the state typically represented by browsers by 'greying out' a
2634- control. Disabled controls are not 'successful' -- they don't cause data
2635- to get returned to the server. Readonly controls usually appear in
2636- browsers as read-only text boxes. Readonly controls are successful. List
2637- items can also be disabled. Attempts to select or deselect disabled items
2638- fail with AttributeError.
2639-
2640- If a lot of controls are readonly, it can be useful to do this:
2641-
2642- form.set_all_readonly(False)
2643-
2644- To clear a control's value attribute, so that it is not successful (until a
2645- value is subsequently set):
2646-
2647- form.clear("cheeses")
2648-
2649- More examples:
2650-
2651- control = form.find_control("cheeses")
2652- control.disabled = False
2653- control.readonly = False
2654- control.get("gruyere").disabled = True
2655- control.items[0].selected = True
2656-
2657- See the various Control classes for further documentation. Many methods
2658- take name, type, kind, id, label and nr arguments to specify the control to
2659- be operated on: see HTMLForm.find_control.__doc__.
2660-
2661- ControlNotFoundError (subclass of ValueError) is raised if the specified
2662- control can't be found. This includes occasions where a non-ListControl
2663- is found, but the method (set, for example) requires a ListControl.
2664- ItemNotFoundError (subclass of ValueError) is raised if a list item can't
2665- be found. ItemCountError (subclass of ValueError) is raised if an attempt
2666- is made to select more than one item and the control doesn't allow that, or
2667- set/get_single are called and the control contains more than one item.
2668- AttributeError is raised if a control or item is readonly or disabled and
2669- an attempt is made to alter its value.
2670-
2671- Security note: Remember that any passwords you store in HTMLForm instances
2672- will be saved to disk in the clear if you pickle them (directly or
2673- indirectly). The simplest solution to this is to avoid pickling HTMLForm
2674- objects. You could also pickle before filling in any password, or just set
2675- the password to "" before pickling.
2676-
2677-
2678- Public attributes:
2679-
2680- action: full (absolute URI) form action
2681- method: "GET" or "POST"
2682- enctype: form transfer encoding MIME type
2683- name: name of form (None if no name was specified)
2684- attrs: dictionary mapping original HTML form attributes to their values
2685-
2686- controls: list of Control instances; do not alter this list
2687- (instead, call form.new_control to make a Control and add it to the
2688- form, or control.add_to_form if you already have a Control instance)
2689-
2690-
2691-
2692- Methods for form filling:
2693- -------------------------
2694-
2695- Most of the these methods have very similar arguments. See
2696- HTMLForm.find_control.__doc__ for details of the name, type, kind, label
2697- and nr arguments.
2698-
2699- def find_control(self,
2700- name=None, type=None, kind=None, id=None, predicate=None,
2701- nr=None, label=None)
2702-
2703- get_value(name=None, type=None, kind=None, id=None, nr=None,
2704- by_label=False, # by_label is deprecated
2705- label=None)
2706- set_value(value,
2707- name=None, type=None, kind=None, id=None, nr=None,
2708- by_label=False, # by_label is deprecated
2709- label=None)
2710-
2711- clear_all()
2712- clear(name=None, type=None, kind=None, id=None, nr=None, label=None)
2713-
2714- set_all_readonly(readonly)
2715-
2716-
2717- Method applying only to FileControls:
2718-
2719- add_file(file_object,
2720- content_type="application/octet-stream", filename=None,
2721- name=None, id=None, nr=None, label=None)
2722-
2723-
2724- Methods applying only to clickable controls:
2725-
2726- click(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
2727- click_request_data(name=None, type=None, id=None, nr=0, coord=(1,1),
2728- label=None)
2729- click_pairs(name=None, type=None, id=None, nr=0, coord=(1,1), label=None)
2730-
2731- """
2732-
2733- type2class = {
2734- "text": TextControl,
2735- "password": PasswordControl,
2736- "hidden": HiddenControl,
2737- "textarea": TextareaControl,
2738-
2739- "isindex": IsindexControl,
2740-
2741- "file": FileControl,
2742-
2743- "button": IgnoreControl,
2744- "buttonbutton": IgnoreControl,
2745- "reset": IgnoreControl,
2746- "resetbutton": IgnoreControl,
2747-
2748- "submit": SubmitControl,
2749- "submitbutton": SubmitButtonControl,
2750- "image": ImageControl,
2751-
2752- "radio": RadioControl,
2753- "checkbox": CheckboxControl,
2754- "select": SelectControl,
2755- }
2756-
2757-#---------------------------------------------------
2758-# Initialisation. Use ParseResponse / ParseFile instead.
2759-
2760- def __init__(self, action, method="GET",
2761- enctype="application/x-www-form-urlencoded",
2762- name=None, attrs=None,
2763- request_class=urllib2.Request,
2764- forms=None, labels=None, id_to_labels=None,
2765- backwards_compat=True):
2766- """
2767- In the usual case, use ParseResponse (or ParseFile) to create new
2768- HTMLForm objects.
2769-
2770- action: full (absolute URI) form action
2771- method: "GET" or "POST"
2772- enctype: form transfer encoding MIME type
2773- name: name of form
2774- attrs: dictionary mapping original HTML form attributes to their values
2775-
2776- """
2777- self.action = action
2778- self.method = method
2779- self.enctype = enctype
2780- self.name = name
2781- if attrs is not None:
2782- self.attrs = attrs.copy()
2783- else:
2784- self.attrs = {}
2785- self.controls = []
2786- self._request_class = request_class
2787-
2788- # these attributes are used by zope.testbrowser
2789- self._forms = forms # this is a semi-public API!
2790- self._labels = labels # this is a semi-public API!
2791- self._id_to_labels = id_to_labels # this is a semi-public API!
2792-
2793- self.backwards_compat = backwards_compat # note __setattr__
2794-
2795- self._urlunparse = urlparse.urlunparse
2796- self._urlparse = urlparse.urlparse
2797-
2798- def __getattr__(self, name):
2799- if name == "backwards_compat":
2800- return self._backwards_compat
2801- return getattr(HTMLForm, name)
2802-
2803- def __setattr__(self, name, value):
2804- # yuck
2805- if name == "backwards_compat":
2806- name = "_backwards_compat"
2807- value = bool(value)
2808- for cc in self.controls:
2809- try:
2810- items = cc.items
2811- except AttributeError:
2812- continue
2813- else:
2814- for ii in items:
2815- for ll in ii.get_labels():
2816- ll._backwards_compat = value
2817- self.__dict__[name] = value
2818-
2819- def new_control(self, type, name, attrs,
2820- ignore_unknown=False, select_default=False, index=None):
2821- """Adds a new control to the form.
2822-
2823- This is usually called by ParseFile and ParseResponse. Don't call it
2824- youself unless you're building your own Control instances.
2825-
2826- Note that controls representing lists of items are built up from
2827- controls holding only a single list item. See ListControl.__doc__ for
2828- further information.
2829-
2830- type: type of control (see Control.__doc__ for a list)
2831- attrs: HTML attributes of control
2832- ignore_unknown: if true, use a dummy Control instance for controls of
2833- unknown type; otherwise, use a TextControl
2834- select_default: for RADIO and multiple-selection SELECT controls, pick
2835- the first item as the default if no 'selected' HTML attribute is
2836- present (this defaulting happens when the HTMLForm.fixup method is
2837- called)
2838- index: index of corresponding element in HTML (see
2839- MoreFormTests.test_interspersed_controls for motivation)
2840-
2841- """
2842- type = type.lower()
2843- klass = self.type2class.get(type)
2844- if klass is None:
2845- if ignore_unknown:
2846- klass = IgnoreControl
2847- else:
2848- klass = TextControl
2849-
2850- a = attrs.copy()
2851- if issubclass(klass, ListControl):
2852- control = klass(type, name, a, select_default, index)
2853- else:
2854- control = klass(type, name, a, index)
2855-
2856- if type == "select" and len(attrs) == 1:
2857- for ii in range(len(self.controls)-1, -1, -1):
2858- ctl = self.controls[ii]
2859- if ctl.type == "select":
2860- ctl.close_control()
2861- break
2862-
2863- control.add_to_form(self)
2864- control._urlparse = self._urlparse
2865- control._urlunparse = self._urlunparse
2866-
2867- def fixup(self):
2868- """Normalise form after all controls have been added.
2869-
2870- This is usually called by ParseFile and ParseResponse. Don't call it
2871- youself unless you're building your own Control instances.
2872-
2873- This method should only be called once, after all controls have been
2874- added to the form.
2875-
2876- """
2877- for control in self.controls:
2878- control.fixup()
2879- self.backwards_compat = self._backwards_compat
2880-
2881-#---------------------------------------------------
2882- def __str__(self):
2883- header = "%s%s %s %s" % (
2884- (self.name and self.name+" " or ""),
2885- self.method, self.action, self.enctype)
2886- rep = [header]
2887- for control in self.controls:
2888- rep.append(" %s" % str(control))
2889- return "<%s>" % "\n".join(rep)
2890-
2891-#---------------------------------------------------
2892-# Form-filling methods.
2893-
2894- def __getitem__(self, name):
2895- return self.find_control(name).value
2896- def __contains__(self, name):
2897- return bool(self.find_control(name))
2898- def __setitem__(self, name, value):
2899- control = self.find_control(name)
2900- try:
2901- control.value = value
2902- except AttributeError, e:
2903- raise ValueError(str(e))
2904-
2905- def get_value(self,
2906- name=None, type=None, kind=None, id=None, nr=None,
2907- by_label=False, # by_label is deprecated
2908- label=None):
2909- """Return value of control.
2910-
2911- If only name and value arguments are supplied, equivalent to
2912-
2913- form[name]
2914-
2915- """
2916- if by_label:
2917- deprecation("form.get_value_by_label(...)")
2918- c = self.find_control(name, type, kind, id, label=label, nr=nr)
2919- if by_label:
2920- try:
2921- meth = c.get_value_by_label
2922- except AttributeError:
2923- raise NotImplementedError(
2924- "control '%s' does not yet support by_label" % c.name)
2925- else:
2926- return meth()
2927- else:
2928- return c.value
2929- def set_value(self, value,
2930- name=None, type=None, kind=None, id=None, nr=None,
2931- by_label=False, # by_label is deprecated
2932- label=None):
2933- """Set value of control.
2934-
2935- If only name and value arguments are supplied, equivalent to
2936-
2937- form[name] = value
2938-
2939- """
2940- if by_label:
2941- deprecation("form.get_value_by_label(...)")
2942- c = self.find_control(name, type, kind, id, label=label, nr=nr)
2943- if by_label:
2944- try:
2945- meth = c.set_value_by_label
2946- except AttributeError:
2947- raise NotImplementedError(
2948- "control '%s' does not yet support by_label" % c.name)
2949- else:
2950- meth(value)
2951- else:
2952- c.value = value
2953- def get_value_by_label(
2954- self, name=None, type=None, kind=None, id=None, label=None, nr=None):
2955- """
2956-
2957- All arguments should be passed by name.
2958-
2959- """
2960- c = self.find_control(name, type, kind, id, label=label, nr=nr)
2961- return c.get_value_by_label()
2962-
2963- def set_value_by_label(
2964- self, value,
2965- name=None, type=None, kind=None, id=None, label=None, nr=None):
2966- """
2967-
2968- All arguments should be passed by name.
2969-
2970- """
2971- c = self.find_control(name, type, kind, id, label=label, nr=nr)
2972- c.set_value_by_label(value)
2973-
2974- def set_all_readonly(self, readonly):
2975- for control in self.controls:
2976- control.readonly = bool(readonly)
2977-
2978- def clear_all(self):
2979- """Clear the value attributes of all controls in the form.
2980-
2981- See HTMLForm.clear.__doc__.
2982-
2983- """
2984- for control in self.controls:
2985- control.clear()
2986-
2987- def clear(self,
2988- name=None, type=None, kind=None, id=None, nr=None, label=None):
2989- """Clear the value attribute of a control.
2990-
2991- As a result, the affected control will not be successful until a value
2992- is subsequently set. AttributeError is raised on readonly controls.
2993-
2994- """
2995- c = self.find_control(name, type, kind, id, label=label, nr=nr)
2996- c.clear()
2997-
2998-
2999-#---------------------------------------------------
3000-# Form-filling methods applying only to ListControls.
3001-
3002- def possible_items(self, # deprecated
3003- name=None, type=None, kind=None, id=None,
3004- nr=None, by_label=False, label=None):
3005- """Return a list of all values that the specified control can take."""
3006- c = self._find_list_control(name, type, kind, id, label, nr)
3007- return c.possible_items(by_label)
3008-
3009- def set(self, selected, item_name, # deprecated
3010- name=None, type=None, kind=None, id=None, nr=None,
3011- by_label=False, label=None):
3012- """Select / deselect named list item.
3013-
3014- selected: boolean selected state
3015-
3016- """
3017- self._find_list_control(name, type, kind, id, label, nr).set(
3018- selected, item_name, by_label)
3019- def toggle(self, item_name, # deprecated
3020- name=None, type=None, kind=None, id=None, nr=None,
3021- by_label=False, label=None):
3022- """Toggle selected state of named list item."""
3023- self._find_list_control(name, type, kind, id, label, nr).toggle(
3024- item_name, by_label)
3025-
3026- def set_single(self, selected, # deprecated
3027- name=None, type=None, kind=None, id=None,
3028- nr=None, by_label=None, label=None):
3029- """Select / deselect list item in a control having only one item.
3030-
3031- If the control has multiple list items, ItemCountError is raised.
3032-
3033- This is just a convenience method, so you don't need to know the item's
3034- name -- the item name in these single-item controls is usually
3035- something meaningless like "1" or "on".
3036-
3037- For example, if a checkbox has a single item named "on", the following
3038- two calls are equivalent:
3039-
3040- control.toggle("on")
3041- control.toggle_single()
3042-
3043- """ # by_label ignored and deprecated
3044- self._find_list_control(
3045- name, type, kind, id, label, nr).set_single(selected)
3046- def toggle_single(self, name=None, type=None, kind=None, id=None,
3047- nr=None, by_label=None, label=None): # deprecated
3048- """Toggle selected state of list item in control having only one item.
3049-
3050- The rest is as for HTMLForm.set_single.__doc__.
3051-
3052- """ # by_label ignored and deprecated
3053- self._find_list_control(name, type, kind, id, label, nr).toggle_single()
3054-
3055-#---------------------------------------------------
3056-# Form-filling method applying only to FileControls.
3057-
3058- def add_file(self, file_object, content_type=None, filename=None,
3059- name=None, id=None, nr=None, label=None):
3060- """Add a file to be uploaded.
3061-
3062- file_object: file-like object (with read method) from which to read
3063- data to upload
3064- content_type: MIME content type of data to upload
3065- filename: filename to pass to server
3066-
3067- If filename is None, no filename is sent to the server.
3068-
3069- If content_type is None, the content type is guessed based on the
3070- filename and the data from read from the file object.
3071-
3072- XXX
3073- At the moment, guessed content type is always application/octet-stream.
3074- Use sndhdr, imghdr modules. Should also try to guess HTML, XML, and
3075- plain text.
3076-
3077- Note the following useful HTML attributes of file upload controls (see
3078- HTML 4.01 spec, section 17):
3079-
3080- accept: comma-separated list of content types that the server will
3081- handle correctly; you can use this to filter out non-conforming files
3082- size: XXX IIRC, this is indicative of whether form wants multiple or
3083- single files
3084- maxlength: XXX hint of max content length in bytes?
3085-
3086- """
3087- self.find_control(name, "file", id=id, label=label, nr=nr).add_file(
3088- file_object, content_type, filename)
3089-
3090-#---------------------------------------------------
3091-# Form submission methods, applying only to clickable controls.
3092-
3093- def click(self, name=None, type=None, id=None, nr=0, coord=(1,1),
3094- request_class=urllib2.Request,
3095- label=None):
3096- """Return request that would result from clicking on a control.
3097-
3098- The request object is a urllib2.Request instance, which you can pass to
3099- urllib2.urlopen (or ClientCookie.urlopen).
3100-
3101- Only some control types (INPUT/SUBMIT & BUTTON/SUBMIT buttons and
3102- IMAGEs) can be clicked.
3103-
3104- Will click on the first clickable control, subject to the name, type
3105- and nr arguments (as for find_control). If no name, type, id or number
3106- is specified and there are no clickable controls, a request will be
3107- returned for the form in its current, un-clicked, state.
3108-
3109- IndexError is raised if any of name, type, id or nr is specified but no
3110- matching control is found. ValueError is raised if the HTMLForm has an
3111- enctype attribute that is not recognised.
3112-
3113- You can optionally specify a coordinate to click at, which only makes a
3114- difference if you clicked on an image.
3115-
3116- """
3117- return self._click(name, type, id, label, nr, coord, "request",
3118- self._request_class)
3119-
3120- def click_request_data(self,
3121- name=None, type=None, id=None,
3122- nr=0, coord=(1,1),
3123- request_class=urllib2.Request,
3124- label=None):
3125- """As for click method, but return a tuple (url, data, headers).
3126-
3127- You can use this data to send a request to the server. This is useful
3128- if you're using httplib or urllib rather than urllib2. Otherwise, use
3129- the click method.
3130-
3131- # Untested. Have to subclass to add headers, I think -- so use urllib2
3132- # instead!
3133- import urllib
3134- url, data, hdrs = form.click_request_data()
3135- r = urllib.urlopen(url, data)
3136-
3137- # Untested. I don't know of any reason to use httplib -- you can get
3138- # just as much control with urllib2.
3139- import httplib, urlparse
3140- url, data, hdrs = form.click_request_data()
3141- tup = urlparse(url)
3142- host, path = tup[1], urlparse.urlunparse((None, None)+tup[2:])
3143- conn = httplib.HTTPConnection(host)
3144- if data:
3145- httplib.request("POST", path, data, hdrs)
3146- else:
3147- httplib.request("GET", path, headers=hdrs)
3148- r = conn.getresponse()
3149-
3150- """
3151- return self._click(name, type, id, label, nr, coord, "request_data",
3152- self._request_class)
3153-
3154- def click_pairs(self, name=None, type=None, id=None,
3155- nr=0, coord=(1,1),
3156- label=None):
3157- """As for click_request_data, but returns a list of (key, value) pairs.
3158-
3159- You can use this list as an argument to ClientForm.urlencode. This is
3160- usually only useful if you're using httplib or urllib rather than
3161- urllib2 or ClientCookie. It may also be useful if you want to manually
3162- tweak the keys and/or values, but this should not be necessary.
3163- Otherwise, use the click method.
3164-
3165- Note that this method is only useful for forms of MIME type
3166- x-www-form-urlencoded. In particular, it does not return the
3167- information required for file upload. If you need file upload and are
3168- not using urllib2, use click_request_data.
3169-
3170- Also note that Python 2.0's urllib.urlencode is slightly broken: it
3171- only accepts a mapping, not a sequence of pairs, as an argument. This
3172- messes up any ordering in the argument. Use ClientForm.urlencode
3173- instead.
3174-
3175- """
3176- return self._click(name, type, id, label, nr, coord, "pairs",
3177- self._request_class)
3178-
3179-#---------------------------------------------------
3180-
3181- def find_control(self,
3182- name=None, type=None, kind=None, id=None,
3183- predicate=None, nr=None,
3184- label=None):
3185- """Locate and return some specific control within the form.
3186-
3187- At least one of the name, type, kind, predicate and nr arguments must
3188- be supplied. If no matching control is found, ControlNotFoundError is
3189- raised.
3190-
3191- If name is specified, then the control must have the indicated name.
3192-
3193- If type is specified then the control must have the specified type (in
3194- addition to the types possible for <input> HTML tags: "text",
3195- "password", "hidden", "submit", "image", "button", "radio", "checkbox",
3196- "file" we also have "reset", "buttonbutton", "submitbutton",
3197- "resetbutton", "textarea", "select" and "isindex").
3198-
3199- If kind is specified, then the control must fall into the specified
3200- group, each of which satisfies a particular interface. The types are
3201- "text", "list", "multilist", "singlelist", "clickable" and "file".
3202-
3203- If id is specified, then the control must have the indicated id.
3204-
3205- If predicate is specified, then the control must match that function.
3206- The predicate function is passed the control as its single argument,
3207- and should return a boolean value indicating whether the control
3208- matched.
3209-
3210- nr, if supplied, is the sequence number of the control (where 0 is the
3211- first). Note that control 0 is the first control matching all the
3212- other arguments (if supplied); it is not necessarily the first control
3213- in the form. If no nr is supplied, AmbiguityError is raised if
3214- multiple controls match the other arguments (unless the
3215- .backwards-compat attribute is true).
3216-
3217- If label is specified, then the control must have this label. Note
3218- that radio controls and checkboxes never have labels: their items do.
3219-
3220- """
3221- if ((name is None) and (type is None) and (kind is None) and
3222- (id is None) and (label is None) and (predicate is None) and
3223- (nr is None)):
3224- raise ValueError(
3225- "at least one argument must be supplied to specify control")
3226- return self._find_control(name, type, kind, id, label, predicate, nr)
3227-
3228-#---------------------------------------------------
3229-# Private methods.
3230-
3231- def _find_list_control(self,
3232- name=None, type=None, kind=None, id=None,
3233- label=None, nr=None):
3234- if ((name is None) and (type is None) and (kind is None) and
3235- (id is None) and (label is None) and (nr is None)):
3236- raise ValueError(
3237- "at least one argument must be supplied to specify control")
3238-
3239- return self._find_control(name, type, kind, id, label,
3240- is_listcontrol, nr)
3241-
3242- def _find_control(self, name, type, kind, id, label, predicate, nr):
3243- if ((name is not None) and (name is not Missing) and
3244- not isstringlike(name)):
3245- raise TypeError("control name must be string-like")
3246- if (type is not None) and not isstringlike(type):
3247- raise TypeError("control type must be string-like")
3248- if (kind is not None) and not isstringlike(kind):
3249- raise TypeError("control kind must be string-like")
3250- if (id is not None) and not isstringlike(id):
3251- raise TypeError("control id must be string-like")
3252- if (label is not None) and not isstringlike(label):
3253- raise TypeError("control label must be string-like")
3254- if (predicate is not None) and not callable(predicate):
3255- raise TypeError("control predicate must be callable")
3256- if (nr is not None) and nr < 0:
3257- raise ValueError("control number must be a positive integer")
3258-
3259- orig_nr = nr
3260- found = None
3261- ambiguous = False
3262- if nr is None and self.backwards_compat:
3263- nr = 0
3264-
3265- for control in self.controls:
3266- if ((name is not None and name != control.name) and
3267- (name is not Missing or control.name is not None)):
3268- continue
3269- if type is not None and type != control.type:
3270- continue
3271- if kind is not None and not control.is_of_kind(kind):
3272- continue
3273- if id is not None and id != control.id:
3274- continue
3275- if predicate and not predicate(control):
3276- continue
3277- if label:
3278- for l in control.get_labels():
3279- if l.text.find(label) > -1:
3280- break
3281- else:
3282- continue
3283- if nr is not None:
3284- if nr == 0:
3285- return control # early exit: unambiguous due to nr
3286- nr -= 1
3287- continue
3288- if found:
3289- ambiguous = True
3290- break
3291- found = control
3292-
3293- if found and not ambiguous:
3294- return found
3295-
3296- description = []
3297- if name is not None: description.append("name %s" % repr(name))
3298- if type is not None: description.append("type '%s'" % type)
3299- if kind is not None: description.append("kind '%s'" % kind)
3300- if id is not None: description.append("id '%s'" % id)
3301- if label is not None: description.append("label '%s'" % label)
3302- if predicate is not None:
3303- description.append("predicate %s" % predicate)
3304- if orig_nr: description.append("nr %d" % orig_nr)
3305- description = ", ".join(description)
3306-
3307- if ambiguous:
3308- raise AmbiguityError("more than one control matching "+description)
3309- elif not found:
3310- raise ControlNotFoundError("no control matching "+description)
3311- assert False
3312-
3313- def _click(self, name, type, id, label, nr, coord, return_type,
3314- request_class=urllib2.Request):
3315- try:
3316- control = self._find_control(
3317- name, type, "clickable", id, label, None, nr)
3318- except ControlNotFoundError:
3319- if ((name is not None) or (type is not None) or (id is not None) or
3320- (nr != 0)):
3321- raise
3322- # no clickable controls, but no control was explicitly requested,
3323- # so return state without clicking any control
3324- return self._switch_click(return_type, request_class)
3325- else:
3326- return control._click(self, coord, return_type, request_class)
3327-
3328- def _pairs(self):
3329- """Return sequence of (key, value) pairs suitable for urlencoding."""
3330- return [(k, v) for (i, k, v, c_i) in self._pairs_and_controls()]
3331-
3332-
3333- def _pairs_and_controls(self):
3334- """Return sequence of (index, key, value, control_index)
3335- of totally ordered pairs suitable for urlencoding.
3336-
3337- control_index is the index of the control in self.controls
3338- """
3339- pairs = []
3340- for control_index in range(len(self.controls)):
3341- control = self.controls[control_index]
3342- for ii, key, val in control._totally_ordered_pairs():
3343- pairs.append((ii, key, val, control_index))
3344-
3345- # stable sort by ONLY first item in tuple
3346- pairs.sort()
3347-
3348- return pairs
3349-
3350- def _request_data(self):
3351- """Return a tuple (url, data, headers)."""
3352- method = self.method.upper()
3353- #scheme, netloc, path, parameters, query, frag = urlparse.urlparse(self.action)
3354- parts = self._urlparse(self.action)
3355- rest, (query, frag) = parts[:-2], parts[-2:]
3356-
3357- if method == "GET":
3358- if self.enctype != "application/x-www-form-urlencoded":
3359- raise ValueError(
3360- "unknown GET form encoding type '%s'" % self.enctype)
3361- parts = rest + (urlencode(self._pairs()), None)
3362- uri = self._urlunparse(parts)
3363- return uri, None, []
3364- elif method == "POST":
3365- parts = rest + (query, None)
3366- uri = self._urlunparse(parts)
3367- if self.enctype == "application/x-www-form-urlencoded":
3368- return (uri, urlencode(self._pairs()),
3369- [("Content-Type", self.enctype)])
3370- elif self.enctype == "multipart/form-data":
3371- data = StringIO()
3372- http_hdrs = []
3373- mw = MimeWriter(data, http_hdrs)
3374- f = mw.startmultipartbody("form-data", add_to_http_hdrs=True,
3375- prefix=0)
3376- for ii, k, v, control_index in self._pairs_and_controls():
3377- self.controls[control_index]._write_mime_data(mw, k, v)
3378- mw.lastpart()
3379- return uri, data.getvalue(), http_hdrs
3380- else:
3381- raise ValueError(
3382- "unknown POST form encoding type '%s'" % self.enctype)
3383- else:
3384- raise ValueError("Unknown method '%s'" % method)
3385-
3386- def _switch_click(self, return_type, request_class=urllib2.Request):
3387- # This is called by HTMLForm and clickable Controls to hide switching
3388- # on return_type.
3389- if return_type == "pairs":
3390- return self._pairs()
3391- elif return_type == "request_data":
3392- return self._request_data()
3393- else:
3394- req_data = self._request_data()
3395- req = request_class(req_data[0], req_data[1])
3396- for key, val in req_data[2]:
3397- add_hdr = req.add_header
3398- if key.lower() == "content-type":
3399- try:
3400- add_hdr = req.add_unredirected_header
3401- except AttributeError:
3402- # pre-2.4 and not using ClientCookie
3403- pass
3404- add_hdr(key, val)
3405- return req
3406
3407=== modified file 'src/ZConfig/__init__.py'
3408--- src/ZConfig/__init__.py 2010-03-01 20:29:16 +0000
3409+++ src/ZConfig/__init__.py 2010-07-01 19:15:41 +0000
3410@@ -1,6 +1,6 @@
3411 ##############################################################################
3412 #
3413-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3414+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3415 # All Rights Reserved.
3416 #
3417 # This software is subject to the provisions of the Zope Public License,
3418
3419=== modified file 'src/ZConfig/cfgparser.py'
3420--- src/ZConfig/cfgparser.py 2009-04-23 15:12:18 +0000
3421+++ src/ZConfig/cfgparser.py 2010-07-01 19:15:41 +0000
3422@@ -1,6 +1,6 @@
3423 ##############################################################################
3424 #
3425-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3426+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3427 # All Rights Reserved.
3428 #
3429 # This software is subject to the provisions of the Zope Public License,
3430
3431=== modified file 'src/ZConfig/cmdline.py'
3432--- src/ZConfig/cmdline.py 2009-04-23 15:12:18 +0000
3433+++ src/ZConfig/cmdline.py 2010-07-01 19:15:41 +0000
3434@@ -1,6 +1,6 @@
3435 ##############################################################################
3436 #
3437-# Copyright (c) 2003 Zope Corporation and Contributors.
3438+# Copyright (c) 2003 Zope Foundation and Contributors.
3439 # All Rights Reserved.
3440 #
3441 # This software is subject to the provisions of the Zope Public License,
3442
3443=== modified file 'src/ZConfig/components/basic/mapping.py'
3444--- src/ZConfig/components/basic/mapping.py 2009-04-23 15:12:18 +0000
3445+++ src/ZConfig/components/basic/mapping.py 2010-07-01 19:15:41 +0000
3446@@ -1,6 +1,6 @@
3447 ##############################################################################
3448 #
3449-# Copyright (c) 2003 Zope Corporation and Contributors.
3450+# Copyright (c) 2003 Zope Foundation and Contributors.
3451 # All Rights Reserved.
3452 #
3453 # This software is subject to the provisions of the Zope Public License,
3454
3455=== modified file 'src/ZConfig/components/basic/tests/test_mapping.py'
3456--- src/ZConfig/components/basic/tests/test_mapping.py 2009-04-23 15:12:18 +0000
3457+++ src/ZConfig/components/basic/tests/test_mapping.py 2010-07-01 19:15:41 +0000
3458@@ -1,6 +1,6 @@
3459 ##############################################################################
3460 #
3461-# Copyright (c) 2003 Zope Corporation and Contributors.
3462+# Copyright (c) 2003 Zope Foundation and Contributors.
3463 # All Rights Reserved.
3464 #
3465 # This software is subject to the provisions of the Zope Public License,
3466
3467=== modified file 'src/ZConfig/components/logger/__init__.py'
3468--- src/ZConfig/components/logger/__init__.py 2009-04-23 15:12:18 +0000
3469+++ src/ZConfig/components/logger/__init__.py 2010-07-01 19:15:41 +0000
3470@@ -1,6 +1,6 @@
3471 ##############################################################################
3472 #
3473-# Copyright (c) 2003 Zope Corporation and Contributors.
3474+# Copyright (c) 2003 Zope Foundation and Contributors.
3475 # All Rights Reserved.
3476 #
3477 # This software is subject to the provisions of the Zope Public License,
3478
3479=== modified file 'src/ZConfig/components/logger/datatypes.py'
3480--- src/ZConfig/components/logger/datatypes.py 2009-04-23 15:12:18 +0000
3481+++ src/ZConfig/components/logger/datatypes.py 2010-07-01 19:15:41 +0000
3482@@ -1,6 +1,6 @@
3483 ##############################################################################
3484 #
3485-# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
3486+# Copyright (c) 2002 Zope Foundation and Contributors.
3487 #
3488 # This software is subject to the provisions of the Zope Public License,
3489 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
3490
3491=== modified file 'src/ZConfig/components/logger/factory.py'
3492--- src/ZConfig/components/logger/factory.py 2009-04-23 15:12:18 +0000
3493+++ src/ZConfig/components/logger/factory.py 2010-07-01 19:15:41 +0000
3494@@ -1,6 +1,6 @@
3495 ##############################################################################
3496 #
3497-# Copyright (c) 2002 Zope Corporation and Contributors.
3498+# Copyright (c) 2002 Zope Foundation and Contributors.
3499 # All Rights Reserved.
3500 #
3501 # This software is subject to the provisions of the Zope Public License,
3502
3503=== modified file 'src/ZConfig/components/logger/handlers.py'
3504--- src/ZConfig/components/logger/handlers.py 2010-03-01 20:29:16 +0000
3505+++ src/ZConfig/components/logger/handlers.py 2010-07-01 19:15:41 +0000
3506@@ -1,6 +1,6 @@
3507 ##############################################################################
3508 #
3509-# Copyright (c) 2003 Zope Corporation and Contributors.
3510+# Copyright (c) 2003 Zope Foundation and Contributors.
3511 # All Rights Reserved.
3512 #
3513 # This software is subject to the provisions of the Zope Public License,
3514@@ -212,7 +212,20 @@
3515 mailhost = host
3516 else:
3517 mailhost = host, port
3518+ kwargs = {}
3519+ if self.section.smtp_username and self.section.smtp_password:
3520+ # Since credentials were only added in py2.6 we use a kwarg to not
3521+ # break compatibility with older py
3522+ if sys.version_info < (2, 6):
3523+ raise ValueError('SMTP auth requires at least Python 2.6.')
3524+ kwargs['credentials'] = (self.section.smtp_username,
3525+ self.section.smtp_password)
3526+ elif (self.section.smtp_username or self.section.smtp_password):
3527+ raise ValueError(
3528+ 'Either both smtp-username and smtp-password or none must be '
3529+ 'given')
3530 return loghandler.SMTPHandler(mailhost,
3531 self.section.fromaddr,
3532 self.section.toaddrs,
3533- self.section.subject)
3534+ self.section.subject,
3535+ **kwargs)
3536
3537=== modified file 'src/ZConfig/components/logger/handlers.xml'
3538--- src/ZConfig/components/logger/handlers.xml 2010-03-01 20:29:16 +0000
3539+++ src/ZConfig/components/logger/handlers.xml 2010-07-01 19:15:41 +0000
3540@@ -79,6 +79,8 @@
3541 <multikey name="to" required="yes" attribute="toaddrs"/>
3542 <key name="subject" default="Message from Zope"/>
3543 <key name="smtp-server" default="localhost" datatype="inet-address"/>
3544+ <key name="smtp-username" default="" datatype="string"/>
3545+ <key name="smtp-password" default="" datatype="string"/>
3546 <key name="format"
3547 default="%(asctime)s %(levelname)s %(name)s %(message)s"
3548 datatype=".log_format"/>
3549
3550=== modified file 'src/ZConfig/components/logger/logger.py'
3551--- src/ZConfig/components/logger/logger.py 2009-04-23 15:12:18 +0000
3552+++ src/ZConfig/components/logger/logger.py 2010-07-01 19:15:41 +0000
3553@@ -1,6 +1,6 @@
3554 ##############################################################################
3555 #
3556-# Copyright (c) 2003 Zope Corporation and Contributors.
3557+# Copyright (c) 2003 Zope Foundation and Contributors.
3558 # All Rights Reserved.
3559 #
3560 # This software is subject to the provisions of the Zope Public License,
3561
3562=== modified file 'src/ZConfig/components/logger/loghandler.py'
3563--- src/ZConfig/components/logger/loghandler.py 2010-03-01 20:29:16 +0000
3564+++ src/ZConfig/components/logger/loghandler.py 2010-07-01 19:15:41 +0000
3565@@ -1,6 +1,6 @@
3566 ##############################################################################
3567 #
3568-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
3569+# Copyright (c) 2001 Zope Foundation and Contributors.
3570 #
3571 # This software is subject to the provisions of the Zope Public License,
3572 # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
3573
3574=== modified file 'src/ZConfig/components/logger/tests/test_logger.py'
3575--- src/ZConfig/components/logger/tests/test_logger.py 2010-03-01 20:29:16 +0000
3576+++ src/ZConfig/components/logger/tests/test_logger.py 2010-07-01 19:15:41 +0000
3577@@ -1,6 +1,6 @@
3578 ##############################################################################
3579 #
3580-# Copyright (c) 2002 Zope Corporation and Contributors.
3581+# Copyright (c) 2002 Zope Foundation and Contributors.
3582 # All Rights Reserved.
3583 #
3584 # This software is subject to the provisions of the Zope Public License,
3585@@ -346,6 +346,54 @@
3586 self.assertEqual(handler.fromaddr, "zlog-user@example.com")
3587 self.assertEqual(handler.level, logging.FATAL)
3588
3589+ def test_with_email_notifier_with_credentials(self):
3590+ try:
3591+ logger = self.check_simple_logger("<eventlog>\n"
3592+ " <email-notifier>\n"
3593+ " to sysadmin@example.com\n"
3594+ " from zlog-user@example.com\n"
3595+ " level fatal\n"
3596+ " smtp-username john\n"
3597+ " smtp-password johnpw\n"
3598+ " </email-notifier>\n"
3599+ "</eventlog>")
3600+ except ValueError:
3601+ if sys.version_info >= (2, 6):
3602+ # For python 2.6 no ValueError must be raised.
3603+ raise
3604+ else:
3605+ # This path must only be reached with python >=2.6
3606+ self.assert_(sys.version_info >= (2, 6))
3607+ handler = logger.handlers[0]
3608+ self.assertEqual(handler.toaddrs, ["sysadmin@example.com"])
3609+ self.assertEqual(handler.fromaddr, "zlog-user@example.com")
3610+ self.assertEqual(handler.fromaddr, "zlog-user@example.com")
3611+ self.assertEqual(handler.level, logging.FATAL)
3612+ self.assertEqual(handler.username, 'john')
3613+ self.assertEqual(handler.password, 'johnpw')
3614+
3615+ def test_with_email_notifier_with_invalid_credentials(self):
3616+ self.assertRaises(ValueError,
3617+ self.check_simple_logger,
3618+ "<eventlog>\n"
3619+ " <email-notifier>\n"
3620+ " to sysadmin@example.com\n"
3621+ " from zlog-user@example.com\n"
3622+ " level fatal\n"
3623+ " smtp-username john\n"
3624+ " </email-notifier>\n"
3625+ "</eventlog>")
3626+ self.assertRaises(ValueError,
3627+ self.check_simple_logger,
3628+ "<eventlog>\n"
3629+ " <email-notifier>\n"
3630+ " to sysadmin@example.com\n"
3631+ " from zlog-user@example.com\n"
3632+ " level fatal\n"
3633+ " smtp-password john\n"
3634+ " </email-notifier>\n"
3635+ "</eventlog>")
3636+
3637 def check_simple_logger(self, text, level=logging.INFO):
3638 conf = self.get_config(text)
3639 self.assert_(conf.eventlog is not None)
3640
3641=== modified file 'src/ZConfig/datatypes.py'
3642--- src/ZConfig/datatypes.py 2009-04-23 15:12:18 +0000
3643+++ src/ZConfig/datatypes.py 2010-07-01 19:15:41 +0000
3644@@ -1,6 +1,6 @@
3645 ##############################################################################
3646 #
3647-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3648+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3649 # All Rights Reserved.
3650 #
3651 # This software is subject to the provisions of the Zope Public License,
3652
3653=== modified file 'src/ZConfig/info.py'
3654--- src/ZConfig/info.py 2009-04-23 15:12:18 +0000
3655+++ src/ZConfig/info.py 2010-07-01 19:15:41 +0000
3656@@ -1,6 +1,6 @@
3657 ##############################################################################
3658 #
3659-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3660+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3661 # All Rights Reserved.
3662 #
3663 # This software is subject to the provisions of the Zope Public License,
3664
3665=== modified file 'src/ZConfig/loader.py'
3666--- src/ZConfig/loader.py 2009-04-23 15:12:18 +0000
3667+++ src/ZConfig/loader.py 2010-07-01 19:15:41 +0000
3668@@ -1,6 +1,6 @@
3669 ##############################################################################
3670 #
3671-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3672+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3673 # All Rights Reserved.
3674 #
3675 # This software is subject to the provisions of the Zope Public License,
3676@@ -15,6 +15,7 @@
3677
3678 import cStringIO
3679 import os.path
3680+import re
3681 import sys
3682 import urllib
3683 import urllib2
3684@@ -128,14 +129,21 @@
3685 url)
3686 return newurl
3687
3688+ # from RFC 3986:
3689+ # schema = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
3690+ _pathsep_rx = re.compile(r"[a-zA-Z][-+.a-zA-Z0-9]*:")
3691+
3692 def isPath(self, s):
3693 """Return True iff 's' should be handled as a filesystem path."""
3694 if ":" in s:
3695 # XXX This assumes that one-character scheme identifiers
3696 # are always Windows drive letters; I don't know of any
3697 # one-character scheme identifiers.
3698- scheme, rest = urllib.splittype(s)
3699- return len(scheme) == 1
3700+ m = self._pathsep_rx.match(s)
3701+ if m is None:
3702+ return True
3703+ # Does it look like a drive letter?
3704+ return len(m.group(0)) == 2
3705 else:
3706 return True
3707
3708
3709=== modified file 'src/ZConfig/matcher.py'
3710--- src/ZConfig/matcher.py 2009-04-23 15:12:18 +0000
3711+++ src/ZConfig/matcher.py 2010-07-01 19:15:41 +0000
3712@@ -1,6 +1,6 @@
3713 ##############################################################################
3714 #
3715-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3716+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3717 # All Rights Reserved.
3718 #
3719 # This software is subject to the provisions of the Zope Public License,
3720
3721=== modified file 'src/ZConfig/schema.py'
3722--- src/ZConfig/schema.py 2010-03-01 20:29:16 +0000
3723+++ src/ZConfig/schema.py 2010-07-01 19:15:41 +0000
3724@@ -1,6 +1,6 @@
3725 ##############################################################################
3726 #
3727-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3728+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3729 # All Rights Reserved.
3730 #
3731 # This software is subject to the provisions of the Zope Public License,
3732
3733=== modified file 'src/ZConfig/schemaless.py'
3734--- src/ZConfig/schemaless.py 2009-04-23 15:12:18 +0000
3735+++ src/ZConfig/schemaless.py 2010-07-01 19:15:41 +0000
3736@@ -1,6 +1,6 @@
3737 ##############################################################################
3738 #
3739-# Copyright (c) 2007 Zope Corporation and Contributors.
3740+# Copyright (c) 2007 Zope Foundation and Contributors.
3741 # All Rights Reserved.
3742 #
3743 # This software is subject to the provisions of the Zope Public License,
3744
3745=== modified file 'src/ZConfig/substitution.py'
3746--- src/ZConfig/substitution.py 2009-04-23 15:12:18 +0000
3747+++ src/ZConfig/substitution.py 2010-07-01 19:15:41 +0000
3748@@ -1,6 +1,6 @@
3749 ##############################################################################
3750 #
3751-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3752+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3753 # All Rights Reserved.
3754 #
3755 # This software is subject to the provisions of the Zope Public License,
3756
3757=== modified file 'src/ZConfig/tests/__init__.py'
3758--- src/ZConfig/tests/__init__.py 2009-04-23 15:12:18 +0000
3759+++ src/ZConfig/tests/__init__.py 2010-07-01 19:15:41 +0000
3760@@ -1,6 +1,6 @@
3761 ##############################################################################
3762 #
3763-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3764+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3765 # All Rights Reserved.
3766 #
3767 # This software is subject to the provisions of the Zope Public License,
3768
3769=== modified file 'src/ZConfig/tests/library/thing/__init__.py'
3770--- src/ZConfig/tests/library/thing/__init__.py 2009-04-23 15:12:18 +0000
3771+++ src/ZConfig/tests/library/thing/__init__.py 2010-07-01 19:15:41 +0000
3772@@ -1,6 +1,6 @@
3773 ##############################################################################
3774 #
3775-# Copyright (c) 2003 Zope Corporation and Contributors.
3776+# Copyright (c) 2003 Zope Foundation and Contributors.
3777 # All Rights Reserved.
3778 #
3779 # This software is subject to the provisions of the Zope Public License,
3780
3781=== modified file 'src/ZConfig/tests/support.py'
3782--- src/ZConfig/tests/support.py 2009-04-23 15:12:18 +0000
3783+++ src/ZConfig/tests/support.py 2010-07-01 19:15:41 +0000
3784@@ -1,6 +1,6 @@
3785 ##############################################################################
3786 #
3787-# Copyright (c) 2003 Zope Corporation and Contributors.
3788+# Copyright (c) 2003 Zope Foundation and Contributors.
3789 # All Rights Reserved.
3790 #
3791 # This software is subject to the provisions of the Zope Public License,
3792
3793=== modified file 'src/ZConfig/tests/test_cfgimports.py'
3794--- src/ZConfig/tests/test_cfgimports.py 2009-04-23 15:12:18 +0000
3795+++ src/ZConfig/tests/test_cfgimports.py 2010-07-01 19:15:41 +0000
3796@@ -1,6 +1,6 @@
3797 ##############################################################################
3798 #
3799-# Copyright (c) 2003 Zope Corporation and Contributors.
3800+# Copyright (c) 2003 Zope Foundation and Contributors.
3801 # All Rights Reserved.
3802 #
3803 # This software is subject to the provisions of the Zope Public License,
3804
3805=== modified file 'src/ZConfig/tests/test_cmdline.py'
3806--- src/ZConfig/tests/test_cmdline.py 2009-04-23 15:12:18 +0000
3807+++ src/ZConfig/tests/test_cmdline.py 2010-07-01 19:15:41 +0000
3808@@ -1,6 +1,6 @@
3809 ##############################################################################
3810 #
3811-# Copyright (c) 2003 Zope Corporation and Contributors.
3812+# Copyright (c) 2003 Zope Foundation and Contributors.
3813 # All Rights Reserved.
3814 #
3815 # This software is subject to the provisions of the Zope Public License,
3816
3817=== modified file 'src/ZConfig/tests/test_config.py'
3818--- src/ZConfig/tests/test_config.py 2009-04-23 15:12:18 +0000
3819+++ src/ZConfig/tests/test_config.py 2010-07-01 19:15:41 +0000
3820@@ -1,6 +1,6 @@
3821 ##############################################################################
3822 #
3823-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3824+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3825 # All Rights Reserved.
3826 #
3827 # This software is subject to the provisions of the Zope Public License,
3828
3829=== modified file 'src/ZConfig/tests/test_cookbook.py'
3830--- src/ZConfig/tests/test_cookbook.py 2009-04-23 15:12:18 +0000
3831+++ src/ZConfig/tests/test_cookbook.py 2010-07-01 19:15:41 +0000
3832@@ -1,6 +1,6 @@
3833 ##############################################################################
3834 #
3835-# Copyright (c) 2004 Zope Corporation and Contributors.
3836+# Copyright (c) 2004 Zope Foundation and Contributors.
3837 # All Rights Reserved.
3838 #
3839 # This software is subject to the provisions of the Zope Public License,
3840
3841=== modified file 'src/ZConfig/tests/test_datatypes.py'
3842--- src/ZConfig/tests/test_datatypes.py 2009-04-23 15:12:18 +0000
3843+++ src/ZConfig/tests/test_datatypes.py 2010-07-01 19:15:41 +0000
3844@@ -1,6 +1,6 @@
3845 ##############################################################################
3846 #
3847-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3848+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3849 # All Rights Reserved.
3850 #
3851 # This software is subject to the provisions of the Zope Public License,
3852
3853=== modified file 'src/ZConfig/tests/test_loader.py'
3854--- src/ZConfig/tests/test_loader.py 2009-04-23 15:12:18 +0000
3855+++ src/ZConfig/tests/test_loader.py 2010-07-01 19:15:41 +0000
3856@@ -1,6 +1,6 @@
3857 ##############################################################################
3858 #
3859-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3860+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3861 # All Rights Reserved.
3862 #
3863 # This software is subject to the provisions of the Zope Public License,
3864@@ -230,6 +230,11 @@
3865 assert_(isPath(r"\abc"))
3866 assert_(isPath(r"\abc\def"))
3867 assert_(isPath(r"c:\abc\def"))
3868+ assert_(isPath("/ab:cd"))
3869+ assert_(isPath(r"\ab:cd"))
3870+ assert_(isPath("long name with spaces"))
3871+ assert_(isPath("long name:with spaces"))
3872+ assert_(not isPath("ab:cd"))
3873 assert_(not isPath("http://www.example.com/"))
3874 assert_(not isPath("http://www.example.com/sample.conf"))
3875 assert_(not isPath("file:///etc/zope/zope.conf"))
3876
3877=== modified file 'src/ZConfig/tests/test_readme.py'
3878--- src/ZConfig/tests/test_readme.py 2010-03-01 20:29:16 +0000
3879+++ src/ZConfig/tests/test_readme.py 2010-07-01 19:15:41 +0000
3880@@ -1,6 +1,6 @@
3881 ##############################################################################
3882 #
3883-# Copyright (c) 2009 Zope Corporation and Contributors.
3884+# Copyright (c) 2009 Zope Foundation and Contributors.
3885 # All Rights Reserved.
3886 #
3887 # This software is subject to the provisions of the Zope Public License,
3888
3889=== modified file 'src/ZConfig/tests/test_schema.py'
3890--- src/ZConfig/tests/test_schema.py 2010-03-01 20:29:16 +0000
3891+++ src/ZConfig/tests/test_schema.py 2010-07-01 19:15:41 +0000
3892@@ -1,6 +1,6 @@
3893 ##############################################################################
3894 #
3895-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3896+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3897 # All Rights Reserved.
3898 #
3899 # This software is subject to the provisions of the Zope Public License,
3900
3901=== modified file 'src/ZConfig/tests/test_schemaless.py'
3902--- src/ZConfig/tests/test_schemaless.py 2009-04-23 15:12:18 +0000
3903+++ src/ZConfig/tests/test_schemaless.py 2010-07-01 19:15:41 +0000
3904@@ -1,6 +1,6 @@
3905 ##############################################################################
3906 #
3907-# Copyright (c) 2007 Zope Corporation and Contributors.
3908+# Copyright (c) 2007 Zope Foundation and Contributors.
3909 # All Rights Reserved.
3910 #
3911 # This software is subject to the provisions of the Zope Public License,
3912
3913=== modified file 'src/ZConfig/tests/test_subst.py'
3914--- src/ZConfig/tests/test_subst.py 2009-04-23 15:12:18 +0000
3915+++ src/ZConfig/tests/test_subst.py 2010-07-01 19:15:41 +0000
3916@@ -1,6 +1,6 @@
3917 ##############################################################################
3918 #
3919-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3920+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3921 # All Rights Reserved.
3922 #
3923 # This software is subject to the provisions of the Zope Public License,
3924
3925=== modified file 'src/ZConfig/url.py'
3926--- src/ZConfig/url.py 2009-04-23 15:12:18 +0000
3927+++ src/ZConfig/url.py 2010-07-01 19:15:41 +0000
3928@@ -1,6 +1,6 @@
3929 ##############################################################################
3930 #
3931-# Copyright (c) 2002, 2003 Zope Corporation and Contributors.
3932+# Copyright (c) 2002, 2003 Zope Foundation and Contributors.
3933 # All Rights Reserved.
3934 #
3935 # This software is subject to the provisions of the Zope Public License,
3936
3937=== modified file 'src/ZEO/cache.py'
3938--- src/ZEO/cache.py 2010-03-01 20:29:16 +0000
3939+++ src/ZEO/cache.py 2010-07-01 19:15:41 +0000
3940@@ -39,19 +39,19 @@
3941 logger = logging.getLogger("ZEO.cache")
3942
3943 # A disk-based cache for ZEO clients.
3944-#
3945+#
3946 # This class provides an interface to a persistent, disk-based cache
3947 # used by ZEO clients to store copies of database records from the
3948 # server.
3949-#
3950+#
3951 # The details of the constructor as unspecified at this point.
3952-#
3953+#
3954 # Each entry in the cache is valid for a particular range of transaction
3955 # ids. The lower bound is the transaction that wrote the data. The
3956 # upper bound is the next transaction that wrote a revision of the
3957 # object. If the data is current, the upper bound is stored as None;
3958 # the data is considered current until an invalidate() call is made.
3959-#
3960+#
3961 # It is an error to call store() twice with the same object without an
3962 # intervening invalidate() to set the upper bound on the first cache
3963 # entry. Perhaps it will be necessary to have a call the removes
3964@@ -59,12 +59,12 @@
3965 # entry.
3966
3967 # Cache verification
3968-#
3969+#
3970 # When the client is connected to the server, it receives
3971 # invalidations every time an object is modified. When the client is
3972 # disconnected then reconnects, it must perform cache verification to make
3973 # sure its cached data is synchronized with the storage's current state.
3974-#
3975+#
3976 # quick verification
3977 # full verification
3978 #
3979@@ -206,7 +206,7 @@
3980 self.f = tempfile.TemporaryFile()
3981 self.f.write(magic+z64)
3982 logger.info("created temporary cache file %r", self.f.name)
3983-
3984+
3985 self._initfile(fsize)
3986
3987 # Statistics: _n_adds, _n_added_bytes,
3988@@ -328,7 +328,7 @@
3989 seek(block_size-5, 1)
3990 sync(self.f)
3991
3992- # There is always data to read and
3993+ # There is always data to read and
3994 assert last and status in ' f1234'
3995 first_free_offset = last
3996 else:
3997@@ -358,7 +358,7 @@
3998 except KeyError:
3999 logger.error("Couldn't find non-current %r", (oid, tid))
4000
4001-
4002+
4003 def clearStats(self):
4004 self._n_adds = self._n_added_bytes = 0
4005 self._n_evicts = self._n_evicted_bytes = 0
4006@@ -471,7 +471,8 @@
4007 return None
4008 self.f.seek(ofs)
4009 read = self.f.read
4010- assert read(1) == 'a', (ofs, self.f.tell(), oid)
4011+ status = read(1)
4012+ assert status == 'a', (ofs, self.f.tell(), oid)
4013 size, saved_oid, tid, end_tid, lver, ldata = unpack(
4014 ">I8s8s8sHI", read(34))
4015 assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
4016@@ -479,7 +480,10 @@
4017
4018 data = read(ldata)
4019 assert len(data) == ldata, (ofs, self.f.tell(), oid, len(data), ldata)
4020- assert read(8) == oid, (ofs, self.f.tell(), oid)
4021+
4022+ # WARNING: The following assert changes the file position.
4023+ # We must not depend on ths below or we'll fail in optimized mode.
4024+ assert read(8) == oid, (ofs, self.f.tell(), oid)
4025
4026 self._n_accesses += 1
4027 self._trace(0x22, oid, tid, end_tid, ldata)
4028@@ -507,7 +511,8 @@
4029
4030 self.f.seek(ofs)
4031 read = self.f.read
4032- assert read(1) == 'a', (ofs, self.f.tell(), oid, before_tid)
4033+ status = read(1)
4034+ assert status == 'a', (ofs, self.f.tell(), oid, before_tid)
4035 size, saved_oid, saved_tid, end_tid, lver, ldata = unpack(
4036 ">I8s8s8sHI", read(34))
4037 assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
4038@@ -516,12 +521,15 @@
4039 assert lver == 0, "Versions aren't supported"
4040 data = read(ldata)
4041 assert len(data) == ldata, (ofs, self.f.tell())
4042+
4043+ # WARNING: The following assert changes the file position.
4044+ # We must not depend on ths below or we'll fail in optimized mode.
4045 assert read(8) == oid, (ofs, self.f.tell(), oid)
4046-
4047+
4048 if end_tid < before_tid:
4049 self._trace(0x24, oid, "", before_tid)
4050 return None
4051-
4052+
4053 self._n_accesses += 1
4054 self._trace(0x26, oid, "", saved_tid)
4055 return data, saved_tid, end_tid
4056@@ -543,7 +551,8 @@
4057 if ofs:
4058 seek(ofs)
4059 read = self.f.read
4060- assert read(1) == 'a', (ofs, self.f.tell(), oid)
4061+ status = read(1)
4062+ assert status == 'a', (ofs, self.f.tell(), oid)
4063 size, saved_oid, saved_tid, end_tid = unpack(
4064 ">I8s8s8s", read(28))
4065 assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
4066@@ -613,7 +622,7 @@
4067 else:
4068 self.current[oid] = ofs
4069 self._trace(0x52, oid, start_tid, dlen=len(data))
4070-
4071+
4072 self.currentofs += size
4073
4074 ##
4075@@ -635,7 +644,7 @@
4076 # to threading issues, that when applying a local
4077 # invalidation after a store, that later invalidations from
4078 # the server may already have arrived.
4079-
4080+
4081 @locked
4082 def invalidate(self, oid, tid, server_invalidation=True):
4083 if tid is not None:
4084@@ -655,7 +664,8 @@
4085
4086 self.f.seek(ofs)
4087 read = self.f.read
4088- assert read(1) == 'a', (ofs, self.f.tell(), oid)
4089+ status = read(1)
4090+ assert status == 'a', (ofs, self.f.tell(), oid)
4091 size, saved_oid, saved_tid, end_tid = unpack(">I8s8s8s", read(28))
4092 assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
4093 assert end_tid == z64, (ofs, self.f.tell(), oid)
4094@@ -685,7 +695,8 @@
4095 self._lock.acquire()
4096 try:
4097 seek(ofs)
4098- assert read(1) == 'a', (ofs, self.f.tell(), oid)
4099+ status = read(1)
4100+ assert status == 'a', (ofs, self.f.tell(), oid)
4101 size, saved_oid, tid, end_tid = unpack(">I8s8s8s", read(28))
4102 assert saved_oid == oid, (ofs, self.f.tell(), oid, saved_oid)
4103 assert end_tid == z64, (ofs, self.f.tell(), oid)
4104@@ -750,7 +761,7 @@
4105 except:
4106 print `tid`, `end_tid`
4107 raise
4108-
4109+
4110 self._trace = _trace
4111 _trace(0x00)
4112
4113
4114=== modified file 'src/ZEO/mkzeoinst.py'
4115--- src/ZEO/mkzeoinst.py 2009-04-23 15:12:18 +0000
4116+++ src/ZEO/mkzeoinst.py 2010-07-01 19:15:41 +0000
4117@@ -11,233 +11,15 @@
4118 # FOR A PARTICULAR PURPOSE.
4119 #
4120 ##############################################################################
4121-"""%(program)s -- create a ZEO instance.
4122-
4123-Usage: %(program)s home [port]
4124-
4125-Given an "instance home directory" <home> and some configuration
4126-options (all of which have default values), create the following:
4127-
4128-<home>/etc/zeo.conf -- ZEO config file
4129-<home>/var/ -- Directory for data files: Data.fs etc.
4130-<home>/log/ -- Directory for log files: zeo.log and zeoctl.log
4131-<home>/bin/runzeo -- the zeo server runner
4132-<home>/bin/zeoctl -- start/stop script (a shim for zeoctl.py)
4133-
4134-The script will not overwrite existing files; instead, it will issue a
4135-warning if an existing file is found that differs from the file that
4136-would be written if it didn't exist.
4137+print """
4138+The mkzeoinst script is no longer supported.
4139+
4140+Install the zope.mkzeoinstance package:
4141+
4142+ http://pypi.python.org/pypi/zope.mkzeoinstance
4143+
4144+and use the installed mkzeointance script that is installed by that package.
4145 """
4146
4147-# WARNING! Several templates and functions here are reused by ZRS.
4148-# So be careful with changes.
4149-
4150-import os
4151 import sys
4152-import stat
4153-import getopt
4154-
4155-zeo_conf_template = """\
4156-# ZEO configuration file
4157-
4158-%%define INSTANCE %(instance_home)s
4159-
4160-<zeo>
4161- address %(port)d
4162- read-only false
4163- invalidation-queue-size 100
4164- # pid-filename $INSTANCE/var/ZEO.pid
4165- # monitor-address PORT
4166- # transaction-timeout SECONDS
4167-</zeo>
4168-
4169-<filestorage 1>
4170- path $INSTANCE/var/Data.fs
4171-</filestorage>
4172-
4173-<eventlog>
4174- level info
4175- <logfile>
4176- path $INSTANCE/log/zeo.log
4177- </logfile>
4178-</eventlog>
4179-
4180-<runner>
4181- program $INSTANCE/bin/runzeo
4182- socket-name $INSTANCE/etc/%(package)s.zdsock
4183- daemon true
4184- forever false
4185- backoff-limit 10
4186- exit-codes 0, 2
4187- directory $INSTANCE
4188- default-to-interactive true
4189- # user zope
4190- python %(python)s
4191- zdrun %(zodb3_home)s/zdaemon/zdrun.py
4192-
4193- # This logfile should match the one in the %(package)s.conf file.
4194- # It is used by zdctl's logtail command, zdrun/zdctl doesn't write it.
4195- logfile $INSTANCE/log/%(package)s.log
4196-</runner>
4197-"""
4198-
4199-zeoctl_template = """\
4200-#!/bin/sh
4201-# %(PACKAGE)s instance control script
4202-
4203-# The following two lines are for chkconfig. On Red Hat Linux (and
4204-# some other systems), you can copy or symlink this script into
4205-# /etc/rc.d/init.d/ and then use chkconfig(8) to automatically start
4206-# %(PACKAGE)s at boot time.
4207-
4208-# chkconfig: 345 90 10
4209-# description: start a %(PACKAGE)s server
4210-
4211-PYTHON="%(python)s"
4212-INSTANCE_HOME="%(instance_home)s"
4213-ZODB3_HOME="%(zodb3_home)s"
4214-
4215-CONFIG_FILE="%(instance_home)s/etc/%(package)s.conf"
4216-
4217-PYTHONPATH="$ZODB3_HOME"
4218-export PYTHONPATH INSTANCE_HOME
4219-
4220-ZEOCTL="$ZODB3_HOME/ZEO/zeoctl.py"
4221-
4222-exec "$PYTHON" "$ZEOCTL" -C "$CONFIG_FILE" ${1+"$@"}
4223-"""
4224-
4225-runzeo_template = """\
4226-#!/bin/sh
4227-# %(PACKAGE)s instance start script
4228-
4229-PYTHON="%(python)s"
4230-INSTANCE_HOME="%(instance_home)s"
4231-ZODB3_HOME="%(zodb3_home)s"
4232-
4233-CONFIG_FILE="%(instance_home)s/etc/%(package)s.conf"
4234-
4235-PYTHONPATH="$ZODB3_HOME"
4236-export PYTHONPATH INSTANCE_HOME
4237-
4238-RUNZEO="$ZODB3_HOME/ZEO/runzeo.py"
4239-
4240-exec "$PYTHON" "$RUNZEO" -C "$CONFIG_FILE" ${1+"$@"}
4241-"""
4242-
4243-def main():
4244- ZEOInstanceBuilder().run()
4245- print "All done."
4246-
4247-class ZEOInstanceBuilder:
4248- def run(self):
4249- try:
4250- opts, args = getopt.getopt(sys.argv[1:], "h", ["help"])
4251- except getopt.error, msg:
4252- print msg
4253- sys.exit(2)
4254- program = os.path.basename(sys.argv[0])
4255- if opts:
4256- # There's only the help options, so just dump some help:
4257- msg = __doc__ % {"program": program}
4258- print msg
4259- sys.exit()
4260- if len(args) not in [1, 2]:
4261- print "Usage: %s home [port]" % program
4262- sys.exit(2)
4263-
4264- instance_home = args[0]
4265- if not os.path.isabs(instance_home):
4266- instance_home = os.path.abspath(instance_home)
4267-
4268- for entry in sys.path:
4269- if os.path.exists(os.path.join(entry, 'ZODB')):
4270- zodb3_home = entry
4271- break
4272- else:
4273- print "Can't find the Zope home (not in sys.path)"
4274- sys.exit(2)
4275-
4276- if args[1:]:
4277- port = int(args[1])
4278- else:
4279- port = 8100 # match example in zope.conf
4280-
4281- params = self.get_params(zodb3_home, instance_home, port)
4282- self.create(instance_home, params)
4283-
4284- def get_params(self, zodb3_home, instance_home, port):
4285- return {
4286- "package": "zeo",
4287- "PACKAGE": "ZEO",
4288- "zodb3_home": zodb3_home,
4289- "instance_home": instance_home,
4290- "port": port,
4291- "python": sys.executable,
4292- }
4293-
4294- def create(self, home, params):
4295- makedir(home)
4296- makedir(home, "etc")
4297- makedir(home, "var")
4298- makedir(home, "log")
4299- makedir(home, "bin")
4300- makefile(zeo_conf_template, home, "etc", "zeo.conf", **params)
4301- makexfile(zeoctl_template, home, "bin", "zeoctl", **params)
4302- makexfile(runzeo_template, home, "bin", "runzeo", **params)
4303-
4304-
4305-def which(program):
4306- strpath = os.getenv("PATH")
4307- binpath = strpath.split(os.pathsep)
4308- for dir in binpath:
4309- path = os.path.join(dir, program)
4310- if os.path.isfile(path) and os.access(path, os.X_OK):
4311- if not os.path.isabs(path):
4312- path = os.path.abspath(path)
4313- return path
4314- raise IOError("can't find %r on path %r" % (program, strpath))
4315-
4316-def makedir(*args):
4317- path = ""
4318- for arg in args:
4319- path = os.path.join(path, arg)
4320- mkdirs(path)
4321- return path
4322-
4323-def mkdirs(path):
4324- if os.path.isdir(path):
4325- return
4326- head, tail = os.path.split(path)
4327- if head and tail and not os.path.isdir(head):
4328- mkdirs(head)
4329- os.mkdir(path)
4330- print "Created directory", path
4331-
4332-def makefile(template, *args, **kwds):
4333- path = makedir(*args[:-1])
4334- path = os.path.join(path, args[-1])
4335- data = template % kwds
4336- if os.path.exists(path):
4337- f = open(path)
4338- olddata = f.read().strip()
4339- f.close()
4340- if olddata:
4341- if olddata != data.strip():
4342- print "Warning: not overwriting existing file %r" % path
4343- return path
4344- f = open(path, "w")
4345- f.write(data)
4346- f.close()
4347- print "Wrote file", path
4348- return path
4349-
4350-def makexfile(template, *args, **kwds):
4351- path = makefile(template, *args, **kwds)
4352- umask = os.umask(022)
4353- os.umask(umask)
4354- mode = 0777 & ~umask
4355- if stat.S_IMODE(os.stat(path)[stat.ST_MODE]) != mode:
4356- os.chmod(path, mode)
4357- print "Changed mode for %s to %o" % (path, mode)
4358- return path
4359+sys.exit(1)
4360
4361=== modified file 'src/ZEO/tests/testZEO.py'
4362--- src/ZEO/tests/testZEO.py 2010-03-01 20:29:16 +0000
4363+++ src/ZEO/tests/testZEO.py 2010-07-01 19:15:41 +0000
4364@@ -486,7 +486,10 @@
4365 # logged
4366 self._storage._connection.handle_request('foo',0,'history',(1,2,3,4))
4367 # test logging
4368- level,message,kw = log[1]
4369+ if __debug__:
4370+ level,message,kw = log[1]
4371+ else:
4372+ level,message,kw = log[0]
4373 self.assertEqual(level,logging.ERROR)
4374 self.failUnless(message.endswith(
4375 ') history() raised exception: history() takes at'
4376
4377=== modified file 'src/ZODB/ConflictResolution.py'
4378--- src/ZODB/ConflictResolution.py 2010-03-01 20:29:16 +0000
4379+++ src/ZODB/ConflictResolution.py 2010-07-01 19:15:41 +0000
4380@@ -116,13 +116,17 @@
4381 # 'm' = multi_persistent: (database_name, oid, klass)
4382 # 'n' = multi_oid: (database_name, oid)
4383 # 'w' = persistent weakref: (oid)
4384+ # or persistent weakref: (oid, database_name)
4385 # else it is a weakref: reference_type
4386 if reference_type == 'm':
4387 self.database_name, self.oid, self.klass = data[1]
4388 elif reference_type == 'n':
4389 self.database_name, self.oid = data[1]
4390 elif reference_type == 'w':
4391- self.oid, = data[1]
4392+ try:
4393+ self.oid, = data[1]
4394+ except ValueError:
4395+ self.oid, self.database_name = data[1]
4396 self.weak = True
4397 else:
4398 assert len(data) == 1, 'unknown reference format'
4399
4400=== modified file 'src/ZODB/ConflictResolution.txt'
4401--- src/ZODB/ConflictResolution.txt 2010-03-01 20:29:16 +0000
4402+++ src/ZODB/ConflictResolution.txt 2010-07-01 19:15:41 +0000
4403@@ -474,6 +474,16 @@
4404 >>> ref3.weak
4405 True
4406
4407+ >>> ref3a = PersistentReference(['w', ('my_oid', 'other_db')])
4408+ >>> ref3a.oid
4409+ 'my_oid'
4410+ >>> print ref3a.klass
4411+ None
4412+ >>> ref3a.database_name
4413+ 'other_db'
4414+ >>> ref3a.weak
4415+ True
4416+
4417 >>> ref4 = PersistentReference(['m', ('other_db', 'my_oid', 'my_class')])
4418 >>> ref4.oid
4419 'my_oid'
4420@@ -508,7 +518,7 @@
4421
4422 >>> ref1 == ref1 and ref2 == ref2 and ref4 == ref4 and ref5 == ref5
4423 True
4424- >>> ref3 == ref3 and ref6 == ref6 # weak references
4425+ >>> ref3 == ref3 and ref3a == ref3a and ref6 == ref6 # weak references
4426 True
4427
4428 Non-weak references with the same oid and database_name are equal.
4429
4430=== modified file 'src/ZODB/Connection.py'
4431--- src/ZODB/Connection.py 2010-03-01 20:29:16 +0000
4432+++ src/ZODB/Connection.py 2010-07-01 19:15:41 +0000
4433@@ -13,7 +13,7 @@
4434 ##############################################################################
4435 """Database connection support
4436
4437-$Id: Connection.py 106502 2009-12-14 19:27:08Z jim $"""
4438+$Id: Connection.py 111314 2010-04-23 17:30:12Z jim $"""
4439
4440 import logging
4441 import sys
4442@@ -1143,8 +1143,9 @@
4443 self._abort()
4444 self._registered_objects = []
4445 src = self._storage
4446- self._cache.invalidate(src.index)
4447+ index = src.index
4448 src.reset(*state)
4449+ self._cache.invalidate(index)
4450
4451 def _commit_savepoint(self, transaction):
4452 """Commit all changes made in savepoints and begin 2-phase commit
4453
4454=== modified file 'src/ZODB/scripts/tests/test_repozo.py'
4455--- src/ZODB/scripts/tests/test_repozo.py 2010-03-01 20:29:16 +0000
4456+++ src/ZODB/scripts/tests/test_repozo.py 2010-07-01 19:15:41 +0000
4457@@ -1,4 +1,3 @@
4458-#!/usr/bin/env python
4459 ##############################################################################
4460 #
4461 # Copyright (c) 2004-2009 Zope Corporation and Contributors.
4462@@ -12,10 +11,9 @@
4463 # FOR A PARTICULAR PURPOSE.
4464 #
4465 ##############################################################################
4466-
4467 import unittest
4468 import os
4469-import ZODB.tests.util
4470+import ZODB.tests.util # layer used at class scope
4471
4472 _NOISY = os.environ.get('NOISY_REPOZO_TEST_OUTPUT')
4473
4474@@ -172,10 +170,6 @@
4475
4476
4477 def test_suite():
4478- suite = unittest.TestSuite()
4479- suite.addTest(unittest.makeSuite(RepozoTests))
4480- return suite
4481-
4482-
4483-if __name__ == '__main__':
4484- unittest.main(defaultTest='test_suite')
4485+ return unittest.TestSuite([
4486+ unittest.makeSuite(RepozoTests),
4487+ ])
4488
4489=== modified file 'src/ZODB/serialize.py'
4490--- src/ZODB/serialize.py 2010-03-01 20:29:16 +0000
4491+++ src/ZODB/serialize.py 2010-07-01 19:15:41 +0000
4492@@ -100,7 +100,8 @@
4493 The following reference types are defined:
4494
4495 'w'
4496- Persistent weak reference. The arguments consist of an oid.
4497+ Persistent weak reference. The arguments consist of an oid
4498+ and optionally a database name.
4499
4500 The following are planned for the future:
4501
4502@@ -298,8 +299,8 @@
4503
4504 oid = obj.oid
4505 if oid is None:
4506- obj = obj() # get the referenced object
4507- oid = obj._p_oid
4508+ target = obj() # get the referenced object
4509+ oid = target._p_oid
4510 if oid is None:
4511 # Here we are causing the object to be saved in
4512 # the database. One could argue that we shouldn't
4513@@ -308,10 +309,16 @@
4514 # assume that the object will be added eventually.
4515
4516 oid = self._jar.new_oid()
4517- obj._p_jar = self._jar
4518- obj._p_oid = oid
4519- self._stack.append(obj)
4520- return ['w', (oid, )]
4521+ target._p_jar = self._jar
4522+ target._p_oid = oid
4523+ self._stack.append(target)
4524+ obj.oid = oid
4525+ obj.dm = target._p_jar
4526+ obj.database_name = obj.dm.db().database_name
4527+ if obj.dm is self._jar:
4528+ return ['w', (oid, )]
4529+ else:
4530+ return ['w', (oid, obj.database_name)]
4531
4532
4533 # Since we have an oid, we have either a persistent instance
4534@@ -530,10 +537,20 @@
4535 loaders['m'] = load_multi_persistent
4536
4537
4538- def load_persistent_weakref(self, oid):
4539+ def load_persistent_weakref(self, oid, database_name=None):
4540 obj = WeakRef.__new__(WeakRef)
4541 obj.oid = oid
4542- obj.dm = self._conn
4543+ if database_name is None:
4544+ obj.dm = self._conn
4545+ else:
4546+ obj.database_name = database_name
4547+ try:
4548+ obj.dm = self._conn.get_connection(database_name)
4549+ except KeyError:
4550+ # XXX Not sure what to do here. It seems wrong to
4551+ # fail since this is a weak reference. For now we'll
4552+ # just pretend that the target object has gone.
4553+ pass
4554 return obj
4555
4556 loaders['w'] = load_persistent_weakref
4557@@ -649,7 +666,7 @@
4558 return oids
4559
4560 oid_klass_loaders = {
4561- 'w': lambda oid: None,
4562+ 'w': lambda oid, database_name=None: None,
4563 }
4564
4565 def get_refs(a_pickle):
4566
4567=== modified file 'src/ZODB/tests/testConnection.py'
4568--- src/ZODB/tests/testConnection.py 2010-03-01 20:29:16 +0000
4569+++ src/ZODB/tests/testConnection.py 2010-07-01 19:15:41 +0000
4570@@ -662,10 +662,26 @@
4571 # sanity check
4572 self.assert_(cache.total_estimated_size >= 0)
4573
4574-
4575-
4576-
4577-
4578+ def test_cache_garbage_collection_shrinking_object(self):
4579+ db = self.db
4580+ # activate size based cache garbage collection
4581+ db.setCacheSizeBytes(1000)
4582+ obj, conn, cache = self.obj, self.conn, self.conn._cache
4583+ # verify the change worked as expected
4584+ self.assertEqual(cache.cache_size_bytes, 1000)
4585+ # verify our entrance assumption is fullfilled
4586+ self.assert_(cache.total_estimated_size > 1)
4587+ # give the objects some size
4588+ obj.setValueWithSize(500)
4589+ transaction.savepoint()
4590+ self.assert_(cache.total_estimated_size > 500)
4591+ # make the object smaller
4592+ obj.setValueWithSize(100)
4593+ transaction.savepoint()
4594+ # make sure there was no overflow
4595+ self.assert_(cache.total_estimated_size != 0)
4596+ # the size is not larger than the allowed maximum
4597+ self.assert_(cache.total_estimated_size <= 1000)
4598
4599 # ---- stubs
4600
4601
4602=== modified file 'src/ZODB/tests/testConnectionSavepoint.py'
4603--- src/ZODB/tests/testConnectionSavepoint.py 2010-03-01 20:29:16 +0000
4604+++ src/ZODB/tests/testConnectionSavepoint.py 2010-07-01 19:15:41 +0000
4605@@ -13,7 +13,7 @@
4606 ##############################################################################
4607 """Tests of savepoint feature
4608
4609-$Id: testConnectionSavepoint.py 103082 2009-08-22 13:36:27Z jim $
4610+$Id: testConnectionSavepoint.py 111314 2010-04-23 17:30:12Z jim $
4611 """
4612 import unittest
4613 from zope.testing import doctest
4614@@ -154,6 +154,34 @@
4615 False
4616 """
4617
4618+class SelfActivatingObject(persistent.Persistent):
4619+
4620+ def _p_invalidate(self):
4621+ super(SelfActivatingObject, self)._p_invalidate()
4622+ self._p_activate()
4623+
4624+def testInvalidateAfterRollback():
4625+ """\
4626+The rollback used to invalidate objects before resetting the TmpStore.
4627+This caused problems for custom _p_invalidate methods that would load
4628+the wrong state.
4629+
4630+ >>> import ZODB.tests.util
4631+ >>> db = ZODB.tests.util.DB()
4632+ >>> connection = db.open()
4633+ >>> root = connection.root()
4634+
4635+ >>> root['p'] = p = SelfActivatingObject()
4636+ >>> transaction.commit()
4637+ >>> p.foo = 1
4638+ >>> sp = transaction.savepoint()
4639+ >>> p.foo = 2
4640+ >>> sp2 = transaction.savepoint()
4641+ >>> sp.rollback()
4642+ >>> p.foo # This used to wrongly return 2
4643+ 1
4644+ """
4645+
4646 def tearDown(test):
4647 transaction.abort()
4648
4649
4650=== added directory 'src/Zope3.egg-info'
4651=== added file 'src/Zope3.egg-info/PKG-INFO'
4652--- src/Zope3.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000
4653+++ src/Zope3.egg-info/PKG-INFO 2010-07-01 19:15:41 +0000
4654@@ -0,0 +1,10 @@
4655+Metadata-Version: 1.0
4656+Name: Zope3
4657+Version: 3.5dev
4658+Summary: Zope3
4659+Home-page: UNKNOWN
4660+Author: Zope Corporation and Contributors
4661+Author-email: zope-dev@zope.org
4662+License: UNKNOWN
4663+Description: UNKNOWN
4664+Platform: UNKNOWN
4665
4666=== added file 'src/Zope3.egg-info/SOURCES.txt'
4667--- src/Zope3.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
4668+++ src/Zope3.egg-info/SOURCES.txt 2010-07-01 19:15:41 +0000
4669@@ -0,0 +1,1924 @@
4670+README.txt
4671+setup.py
4672+src/BTrees/IFBTree.py
4673+src/BTrees/IIBTree.py
4674+src/BTrees/IOBTree.py
4675+src/BTrees/Interfaces.py
4676+src/BTrees/LFBTree.py
4677+src/BTrees/LLBTree.py
4678+src/BTrees/LOBTree.py
4679+src/BTrees/Length.py
4680+src/BTrees/OIBTree.py
4681+src/BTrees/OLBTree.py
4682+src/BTrees/OOBTree.py
4683+src/BTrees/_IFBTree.c
4684+src/BTrees/_IFBTree.py
4685+src/BTrees/_IIBTree.c
4686+src/BTrees/_IIBTree.py
4687+src/BTrees/_IOBTree.c
4688+src/BTrees/_IOBTree.py
4689+src/BTrees/_LFBTree.c
4690+src/BTrees/_LFBTree.py
4691+src/BTrees/_LLBTree.c
4692+src/BTrees/_LLBTree.py
4693+src/BTrees/_LOBTree.c
4694+src/BTrees/_LOBTree.py
4695+src/BTrees/_OIBTree.c
4696+src/BTrees/_OIBTree.py
4697+src/BTrees/_OLBTree.c
4698+src/BTrees/_OLBTree.py
4699+src/BTrees/_OOBTree.c
4700+src/BTrees/_OOBTree.py
4701+src/BTrees/__init__.py
4702+src/BTrees/_fsBTree.c
4703+src/BTrees/_fsBTree.py
4704+src/BTrees/check.py
4705+src/BTrees/fsBTree.py
4706+src/BTrees/tests/__init__.py
4707+src/BTrees/tests/testBTrees.py
4708+src/BTrees/tests/testBTreesUnicode.py
4709+src/BTrees/tests/testConflict.py
4710+src/BTrees/tests/testLength.py
4711+src/BTrees/tests/testSetOps.py
4712+src/BTrees/tests/test_btreesubclass.py
4713+src/BTrees/tests/test_check.py
4714+src/BTrees/tests/test_compare.py
4715+src/RestrictedPython/Eval.py
4716+src/RestrictedPython/Guards.py
4717+src/RestrictedPython/Limits.py
4718+src/RestrictedPython/MutatingWalker.py
4719+src/RestrictedPython/PrintCollector.py
4720+src/RestrictedPython/RCompile.py
4721+src/RestrictedPython/RestrictionMutator.py
4722+src/RestrictedPython/SelectCompiler.py
4723+src/RestrictedPython/Utilities.py
4724+src/RestrictedPython/__init__.py
4725+src/RestrictedPython/tests/__init__.py
4726+src/RestrictedPython/tests/before_and_after.py
4727+src/RestrictedPython/tests/before_and_after24.py
4728+src/RestrictedPython/tests/before_and_after25.py
4729+src/RestrictedPython/tests/before_and_after26.py
4730+src/RestrictedPython/tests/class.py
4731+src/RestrictedPython/tests/lambda.py
4732+src/RestrictedPython/tests/restricted_module.py
4733+src/RestrictedPython/tests/security_in_syntax.py
4734+src/RestrictedPython/tests/security_in_syntax26.py
4735+src/RestrictedPython/tests/testCompile.py
4736+src/RestrictedPython/tests/testREADME.py
4737+src/RestrictedPython/tests/testRestrictions.py
4738+src/RestrictedPython/tests/testUtiliities.py
4739+src/RestrictedPython/tests/unpack.py
4740+src/RestrictedPython/tests/verify.py
4741+src/ZConfig/__init__.py
4742+src/ZConfig/cfgparser.py
4743+src/ZConfig/cmdline.py
4744+src/ZConfig/datatypes.py
4745+src/ZConfig/info.py
4746+src/ZConfig/loader.py
4747+src/ZConfig/matcher.py
4748+src/ZConfig/schema.py
4749+src/ZConfig/schemaless.py
4750+src/ZConfig/substitution.py
4751+src/ZConfig/url.py
4752+src/ZConfig/components/__init__.py
4753+src/ZConfig/components/basic/__init__.py
4754+src/ZConfig/components/basic/mapping.py
4755+src/ZConfig/components/basic/tests/__init__.py
4756+src/ZConfig/components/basic/tests/test_mapping.py
4757+src/ZConfig/components/logger/__init__.py
4758+src/ZConfig/components/logger/datatypes.py
4759+src/ZConfig/components/logger/factory.py
4760+src/ZConfig/components/logger/handlers.py
4761+src/ZConfig/components/logger/logger.py
4762+src/ZConfig/components/logger/loghandler.py
4763+src/ZConfig/components/logger/tests/__init__.py
4764+src/ZConfig/components/logger/tests/test_logger.py
4765+src/ZConfig/tests/__init__.py
4766+src/ZConfig/tests/support.py
4767+src/ZConfig/tests/test_cfgimports.py
4768+src/ZConfig/tests/test_cmdline.py
4769+src/ZConfig/tests/test_config.py
4770+src/ZConfig/tests/test_cookbook.py
4771+src/ZConfig/tests/test_datatypes.py
4772+src/ZConfig/tests/test_loader.py
4773+src/ZConfig/tests/test_readme.py
4774+src/ZConfig/tests/test_schema.py
4775+src/ZConfig/tests/test_schemaless.py
4776+src/ZConfig/tests/test_subst.py
4777+src/ZConfig/tests/library/__init__.py
4778+src/ZConfig/tests/library/thing/__init__.py
4779+src/ZConfig/tests/library/widget/__init__.py
4780+src/ZEO/ClientStorage.py
4781+src/ZEO/CommitLog.py
4782+src/ZEO/Exceptions.py
4783+src/ZEO/ServerStub.py
4784+src/ZEO/StorageServer.py
4785+src/ZEO/TransactionBuffer.py
4786+src/ZEO/__init__.py
4787+src/ZEO/cache.py
4788+src/ZEO/hash.py
4789+src/ZEO/interfaces.py
4790+src/ZEO/mkzeoinst.py
4791+src/ZEO/monitor.py
4792+src/ZEO/runzeo.py
4793+src/ZEO/util.py
4794+src/ZEO/zeoctl.py
4795+src/ZEO/zeopasswd.py
4796+src/ZEO/auth/__init__.py
4797+src/ZEO/auth/auth_digest.py
4798+src/ZEO/auth/base.py
4799+src/ZEO/auth/hmac.py
4800+src/ZEO/scripts/__init__.py
4801+src/ZEO/scripts/parsezeolog.py
4802+src/ZEO/scripts/tests.py
4803+src/ZEO/scripts/timeout.py
4804+src/ZEO/scripts/zeopack.py
4805+src/ZEO/scripts/zeoqueue.py
4806+src/ZEO/scripts/zeoreplay.py
4807+src/ZEO/scripts/zeoserverlog.py
4808+src/ZEO/scripts/zeoup.py
4809+src/ZEO/tests/Cache.py
4810+src/ZEO/tests/CommitLockTests.py
4811+src/ZEO/tests/ConnectionTests.py
4812+src/ZEO/tests/InvalidationTests.py
4813+src/ZEO/tests/IterationTests.py
4814+src/ZEO/tests/TestThread.py
4815+src/ZEO/tests/ThreadTests.py
4816+src/ZEO/tests/__init__.py
4817+src/ZEO/tests/auth_plaintext.py
4818+src/ZEO/tests/deadlock.py
4819+src/ZEO/tests/forker.py
4820+src/ZEO/tests/servertesting.py
4821+src/ZEO/tests/speed.py
4822+src/ZEO/tests/stress.py
4823+src/ZEO/tests/testAuth.py
4824+src/ZEO/tests/testConnection.py
4825+src/ZEO/tests/testConversionSupport.py
4826+src/ZEO/tests/testMonitor.py
4827+src/ZEO/tests/testTransactionBuffer.py
4828+src/ZEO/tests/testZEO.py
4829+src/ZEO/tests/testZEO2.py
4830+src/ZEO/tests/testZEOOptions.py
4831+src/ZEO/tests/test_cache.py
4832+src/ZEO/tests/zeoserver.py
4833+src/ZEO/zrpc/__init__.py
4834+src/ZEO/zrpc/_hmac.py
4835+src/ZEO/zrpc/client.py
4836+src/ZEO/zrpc/connection.py
4837+src/ZEO/zrpc/error.py
4838+src/ZEO/zrpc/log.py
4839+src/ZEO/zrpc/marshal.py
4840+src/ZEO/zrpc/server.py
4841+src/ZEO/zrpc/smac.py
4842+src/ZEO/zrpc/trigger.py
4843+src/ZODB/ActivityMonitor.py
4844+src/ZODB/BaseStorage.py
4845+src/ZODB/ConflictResolution.py
4846+src/ZODB/Connection.py
4847+src/ZODB/DB.py
4848+src/ZODB/DemoStorage.py
4849+src/ZODB/ExportImport.py
4850+src/ZODB/MappingStorage.py
4851+src/ZODB/POSException.py
4852+src/ZODB/UndoLogCompatible.py
4853+src/ZODB/__init__.py
4854+src/ZODB/blob.py
4855+src/ZODB/broken.py
4856+src/ZODB/config.py
4857+src/ZODB/conversionhack.py
4858+src/ZODB/dbmStorage.py
4859+src/ZODB/fsIndex.py
4860+src/ZODB/fsrecover.py
4861+src/ZODB/fstools.py
4862+src/ZODB/interfaces.py
4863+src/ZODB/loglevels.py
4864+src/ZODB/persistentclass.py
4865+src/ZODB/serialize.py
4866+src/ZODB/transact.py
4867+src/ZODB/utils.py
4868+src/ZODB/FileStorage/FileStorage.py
4869+src/ZODB/FileStorage/__init__.py
4870+src/ZODB/FileStorage/format.py
4871+src/ZODB/FileStorage/fsdump.py
4872+src/ZODB/FileStorage/fsoids.py
4873+src/ZODB/FileStorage/fspack.py
4874+src/ZODB/FileStorage/interfaces.py
4875+src/ZODB/FileStorage/tests.py
4876+src/ZODB/scripts/__init__.py
4877+src/ZODB/scripts/analyze.py
4878+src/ZODB/scripts/checkbtrees.py
4879+src/ZODB/scripts/fsoids.py
4880+src/ZODB/scripts/fsrefs.py
4881+src/ZODB/scripts/fsstats.py
4882+src/ZODB/scripts/fstail.py
4883+src/ZODB/scripts/fstest.py
4884+src/ZODB/scripts/migrate.py
4885+src/ZODB/scripts/migrateblobs.py
4886+src/ZODB/scripts/netspace.py
4887+src/ZODB/scripts/referrers.py
4888+src/ZODB/scripts/repozo.py
4889+src/ZODB/scripts/simul.py
4890+src/ZODB/scripts/space.py
4891+src/ZODB/scripts/stats.py
4892+src/ZODB/scripts/zodbload.py
4893+src/ZODB/scripts/tests/__init__.py
4894+src/ZODB/scripts/tests/test_doc.py
4895+src/ZODB/scripts/tests/test_repozo.py
4896+src/ZODB/tests/BasicStorage.py
4897+src/ZODB/tests/ConflictResolution.py
4898+src/ZODB/tests/Corruption.py
4899+src/ZODB/tests/HistoryStorage.py
4900+src/ZODB/tests/IteratorStorage.py
4901+src/ZODB/tests/MTStorage.py
4902+src/ZODB/tests/MVCCMappingStorage.py
4903+src/ZODB/tests/MinPO.py
4904+src/ZODB/tests/PackableStorage.py
4905+src/ZODB/tests/PersistentStorage.py
4906+src/ZODB/tests/ReadOnlyStorage.py
4907+src/ZODB/tests/RecoveryStorage.py
4908+src/ZODB/tests/RevisionStorage.py
4909+src/ZODB/tests/StorageTestBase.py
4910+src/ZODB/tests/Synchronization.py
4911+src/ZODB/tests/TransactionalUndoStorage.py
4912+src/ZODB/tests/__init__.py
4913+src/ZODB/tests/dangle.py
4914+src/ZODB/tests/loggingsupport.py
4915+src/ZODB/tests/sampledm.py
4916+src/ZODB/tests/speed.py
4917+src/ZODB/tests/testActivityMonitor.py
4918+src/ZODB/tests/testBroken.py
4919+src/ZODB/tests/testCache.py
4920+src/ZODB/tests/testConfig.py
4921+src/ZODB/tests/testConnection.py
4922+src/ZODB/tests/testConnectionSavepoint.py
4923+src/ZODB/tests/testDB.py
4924+src/ZODB/tests/testDemoStorage.py
4925+src/ZODB/tests/testFileStorage.py
4926+src/ZODB/tests/testMVCCMappingStorage.py
4927+src/ZODB/tests/testMappingStorage.py
4928+src/ZODB/tests/testPersistentList.py
4929+src/ZODB/tests/testPersistentMapping.py
4930+src/ZODB/tests/testRecover.py
4931+src/ZODB/tests/testSerialize.py
4932+src/ZODB/tests/testTimeStamp.py
4933+src/ZODB/tests/testUtils.py
4934+src/ZODB/tests/testZODB.py
4935+src/ZODB/tests/test_cache.py
4936+src/ZODB/tests/test_datamanageradapter.py
4937+src/ZODB/tests/test_doctest_files.py
4938+src/ZODB/tests/test_fsdump.py
4939+src/ZODB/tests/test_storage.py
4940+src/ZODB/tests/testblob.py
4941+src/ZODB/tests/testconflictresolution.py
4942+src/ZODB/tests/testcrossdatabasereferences.py
4943+src/ZODB/tests/testfsIndex.py
4944+src/ZODB/tests/testfsoids.py
4945+src/ZODB/tests/testhistoricalconnections.py
4946+src/ZODB/tests/testmvcc.py
4947+src/ZODB/tests/testpersistentclass.py
4948+src/ZODB/tests/util.py
4949+src/ZODB/tests/warnhook.py
4950+src/Zope3.egg-info/PKG-INFO
4951+src/Zope3.egg-info/SOURCES.txt
4952+src/Zope3.egg-info/dependency_links.txt
4953+src/Zope3.egg-info/namespace_packages.txt
4954+src/Zope3.egg-info/not-zip-safe
4955+src/Zope3.egg-info/top_level.txt
4956+src/docutils/__init__.py
4957+src/docutils/_compat.py
4958+src/docutils/_string_template_compat.py
4959+src/docutils/core.py
4960+src/docutils/examples.py
4961+src/docutils/frontend.py
4962+src/docutils/io.py
4963+src/docutils/nodes.py
4964+src/docutils/statemachine.py
4965+src/docutils/urischemes.py
4966+src/docutils/utils.py
4967+src/docutils/languages/__init__.py
4968+src/docutils/languages/af.py
4969+src/docutils/languages/ca.py
4970+src/docutils/languages/cs.py
4971+src/docutils/languages/de.py
4972+src/docutils/languages/en.py
4973+src/docutils/languages/eo.py
4974+src/docutils/languages/es.py
4975+src/docutils/languages/fi.py
4976+src/docutils/languages/fr.py
4977+src/docutils/languages/gl.py
4978+src/docutils/languages/he.py
4979+src/docutils/languages/it.py
4980+src/docutils/languages/ja.py
4981+src/docutils/languages/nl.py
4982+src/docutils/languages/pl.py
4983+src/docutils/languages/pt_br.py
4984+src/docutils/languages/ru.py
4985+src/docutils/languages/sk.py
4986+src/docutils/languages/sv.py
4987+src/docutils/languages/zh_cn.py
4988+src/docutils/languages/zh_tw.py
4989+src/docutils/parsers/__init__.py
4990+src/docutils/parsers/null.py
4991+src/docutils/parsers/rst/__init__.py
4992+src/docutils/parsers/rst/roles.py
4993+src/docutils/parsers/rst/states.py
4994+src/docutils/parsers/rst/tableparser.py
4995+src/docutils/parsers/rst/directives/__init__.py
4996+src/docutils/parsers/rst/directives/admonitions.py
4997+src/docutils/parsers/rst/directives/body.py
4998+src/docutils/parsers/rst/directives/html.py
4999+src/docutils/parsers/rst/directives/images.py
5000+src/docutils/parsers/rst/directives/misc.py
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: