Merge lp:~sidnei/zope3/ztk-1.0a1 into lp:~landscape/zope3/trunk
- ztk-1.0a1
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Muharem Hrnjadovic (community) | Approve | ||
Thomas Herve (community) | Approve | ||
Review via email: mp+29032@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
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 {"&": "&", ...} 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.
+1!