pkg://TurboGears-1.0.4.2-3.fc7.src.rpm:1112792/TurboGears-1.0.4.2.tar.gz
info downloads
TurboGears-1.0.4.2/ 0000755 0001750 0001750 00000000000 10745210077 012636 5 ustar faide faide TurboGears-1.0.4.2/LICENSE.txt 0000644 0001750 0001750 00000002260 10710636656 014470 0 ustar faide faide This is the MIT license:
http://www.opensource.org/licenses/mit-license.php
Copyright (c) 2005-2007 Kevin Dangoor and contributors. TurboGears is a trademark of Kevin Dangoor.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TurboGears-1.0.4.2/TurboGears.egg-info/ 0000755 0001750 0001750 00000000000 10745210077 016405 5 ustar faide faide TurboGears-1.0.4.2/TurboGears.egg-info/SOURCES.txt 0000644 0001750 0001750 00000060376 10745210076 020304 0 ustar faide faide CHANGELOG.txt
CONTRIBUTORS.txt
LICENSE.txt
README.txt
codename.py
ez_setup.py
setup.cfg
setup.py
toolbox-start.py
TurboGears.egg-info/PKG-INFO
TurboGears.egg-info/SOURCES.txt
TurboGears.egg-info/dependency_links.txt
TurboGears.egg-info/entry_points.txt
TurboGears.egg-info/not-zip-safe
TurboGears.egg-info/requires.txt
TurboGears.egg-info/top_level.txt
apigen/setup.py
apigen/apigen/__init__.py
apigen/apigen/colorizer.py
apigen/apigen/command.py
apigen/apigen/element_colorizer.py
apigen/apigen/pythondoc.py
apigen/apigen/release.py
apigen/apigen.egg-info/PKG-INFO
apigen/apigen.egg-info/SOURCES.txt
apigen/apigen.egg-info/entry_points.txt
apigen/apigen.egg-info/not-zip-safe
apigen/apigen.egg-info/top_level.txt
thirdparty/externals.py
turbogears/__init__.py
turbogears/config.py
turbogears/controllers.py
turbogears/database.py
turbogears/decorator.py
turbogears/docgen.py
turbogears/errorhandling.py
turbogears/finddata.py
turbogears/genericfunctions.py
turbogears/paginate.py
turbogears/release.py
turbogears/scheduler.py
turbogears/startup.py
turbogears/testutil.py
turbogears/util.py
turbogears/validators.py
turbogears/command/__init__.py
turbogears/command/base.py
turbogears/command/i18n.py
turbogears/command/info.py
turbogears/command/quickstart.py
turbogears/command/sacommand.py
turbogears/feed/__init__.py
turbogears/feed/atom0_3.kid
turbogears/feed/atom1_0.kid
turbogears/feed/feed.py
turbogears/feed/rss2_0.kid
turbogears/i18n/__init__.py
turbogears/i18n/format.py
turbogears/i18n/kidutils.py
turbogears/i18n/tg_gettext.py
turbogears/i18n/utils.py
turbogears/i18n/data/TurboGears.pot
turbogears/i18n/data/__init__.py
turbogears/i18n/data/af.py
turbogears/i18n/data/af_ZA.py
turbogears/i18n/data/am.py
turbogears/i18n/data/am_ET.py
turbogears/i18n/data/ar.py
turbogears/i18n/data/ar_AE.py
turbogears/i18n/data/ar_BH.py
turbogears/i18n/data/ar_DZ.py
turbogears/i18n/data/ar_EG.py
turbogears/i18n/data/ar_IN.py
turbogears/i18n/data/ar_IQ.py
turbogears/i18n/data/ar_JO.py
turbogears/i18n/data/ar_KW.py
turbogears/i18n/data/ar_LB.py
turbogears/i18n/data/ar_LY.py
turbogears/i18n/data/ar_MA.py
turbogears/i18n/data/ar_OM.py
turbogears/i18n/data/ar_QA.py
turbogears/i18n/data/ar_SA.py
turbogears/i18n/data/ar_SD.py
turbogears/i18n/data/ar_SY.py
turbogears/i18n/data/ar_TN.py
turbogears/i18n/data/ar_YE.py
turbogears/i18n/data/be.py
turbogears/i18n/data/be_BY.py
turbogears/i18n/data/bg.py
turbogears/i18n/data/bg_BG.py
turbogears/i18n/data/bn.py
turbogears/i18n/data/bn_IN.py
turbogears/i18n/data/ca.py
turbogears/i18n/data/ca_ES.py
turbogears/i18n/data/cs.py
turbogears/i18n/data/cs_CZ.py
turbogears/i18n/data/da.py
turbogears/i18n/data/da_DK.py
turbogears/i18n/data/de.py
turbogears/i18n/data/de_AT.py
turbogears/i18n/data/de_BE.py
turbogears/i18n/data/de_CH.py
turbogears/i18n/data/de_DE.py
turbogears/i18n/data/de_LU.py
turbogears/i18n/data/el.py
turbogears/i18n/data/el_GR.py
turbogears/i18n/data/en.py
turbogears/i18n/data/en_AU.py
turbogears/i18n/data/en_BE.py
turbogears/i18n/data/en_BW.py
turbogears/i18n/data/en_CA.py
turbogears/i18n/data/en_GB.py
turbogears/i18n/data/en_HK.py
turbogears/i18n/data/en_IE.py
turbogears/i18n/data/en_IN.py
turbogears/i18n/data/en_MT.py
turbogears/i18n/data/en_NZ.py
turbogears/i18n/data/en_PH.py
turbogears/i18n/data/en_SG.py
turbogears/i18n/data/en_US.py
turbogears/i18n/data/en_US_POSIX.py
turbogears/i18n/data/en_VI.py
turbogears/i18n/data/en_ZA.py
turbogears/i18n/data/en_ZW.py
turbogears/i18n/data/eo.py
turbogears/i18n/data/es.py
turbogears/i18n/data/es_AR.py
turbogears/i18n/data/es_BO.py
turbogears/i18n/data/es_CL.py
turbogears/i18n/data/es_CO.py
turbogears/i18n/data/es_CR.py
turbogears/i18n/data/es_DO.py
turbogears/i18n/data/es_EC.py
turbogears/i18n/data/es_ES.py
turbogears/i18n/data/es_GT.py
turbogears/i18n/data/es_HN.py
turbogears/i18n/data/es_MX.py
turbogears/i18n/data/es_NI.py
turbogears/i18n/data/es_PA.py
turbogears/i18n/data/es_PE.py
turbogears/i18n/data/es_PR.py
turbogears/i18n/data/es_PY.py
turbogears/i18n/data/es_SV.py
turbogears/i18n/data/es_US.py
turbogears/i18n/data/es_UY.py
turbogears/i18n/data/es_VE.py
turbogears/i18n/data/et.py
turbogears/i18n/data/et_EE.py
turbogears/i18n/data/eu.py
turbogears/i18n/data/eu_ES.py
turbogears/i18n/data/fa.py
turbogears/i18n/data/fa_AF.py
turbogears/i18n/data/fa_IR.py
turbogears/i18n/data/fi.py
turbogears/i18n/data/fi_FI.py
turbogears/i18n/data/fo.py
turbogears/i18n/data/fo_FO.py
turbogears/i18n/data/fr.py
turbogears/i18n/data/fr_BE.py
turbogears/i18n/data/fr_CA.py
turbogears/i18n/data/fr_CH.py
turbogears/i18n/data/fr_FR.py
turbogears/i18n/data/fr_LU.py
turbogears/i18n/data/ga.py
turbogears/i18n/data/ga_IE.py
turbogears/i18n/data/gl.py
turbogears/i18n/data/gl_ES.py
turbogears/i18n/data/gu.py
turbogears/i18n/data/gu_IN.py
turbogears/i18n/data/gv.py
turbogears/i18n/data/gv_GB.py
turbogears/i18n/data/he.py
turbogears/i18n/data/he_IL.py
turbogears/i18n/data/hi.py
turbogears/i18n/data/hi_IN.py
turbogears/i18n/data/hr.py
turbogears/i18n/data/hr_HR.py
turbogears/i18n/data/hu.py
turbogears/i18n/data/hu_HU.py
turbogears/i18n/data/hy.py
turbogears/i18n/data/hy_AM.py
turbogears/i18n/data/hy_AM_REVISED.py
turbogears/i18n/data/id.py
turbogears/i18n/data/id_ID.py
turbogears/i18n/data/is.py
turbogears/i18n/data/is_IS.py
turbogears/i18n/data/it.py
turbogears/i18n/data/it_CH.py
turbogears/i18n/data/it_IT.py
turbogears/i18n/data/ja.py
turbogears/i18n/data/ja_JP.py
turbogears/i18n/data/kk.py
turbogears/i18n/data/kk_KZ.py
turbogears/i18n/data/kl.py
turbogears/i18n/data/kl_GL.py
turbogears/i18n/data/kn.py
turbogears/i18n/data/kn_IN.py
turbogears/i18n/data/ko.py
turbogears/i18n/data/ko_KR.py
turbogears/i18n/data/kok.py
turbogears/i18n/data/kok_IN.py
turbogears/i18n/data/kw.py
turbogears/i18n/data/kw_GB.py
turbogears/i18n/data/lt.py
turbogears/i18n/data/lt_LT.py
turbogears/i18n/data/lv.py
turbogears/i18n/data/lv_LV.py
turbogears/i18n/data/mk.py
turbogears/i18n/data/mk_MK.py
turbogears/i18n/data/mr.py
turbogears/i18n/data/mr_IN.py
turbogears/i18n/data/ms.py
turbogears/i18n/data/ms_BN.py
turbogears/i18n/data/ms_MY.py
turbogears/i18n/data/mt.py
turbogears/i18n/data/mt_MT.py
turbogears/i18n/data/nb.py
turbogears/i18n/data/nb_NO.py
turbogears/i18n/data/nl.py
turbogears/i18n/data/nl_BE.py
turbogears/i18n/data/nl_NL.py
turbogears/i18n/data/nn.py
turbogears/i18n/data/nn_NO.py
turbogears/i18n/data/no.py
turbogears/i18n/data/no_NO.py
turbogears/i18n/data/om.py
turbogears/i18n/data/om_ET.py
turbogears/i18n/data/om_KE.py
turbogears/i18n/data/pa.py
turbogears/i18n/data/pa_IN.py
turbogears/i18n/data/pl.py
turbogears/i18n/data/pl_PL.py
turbogears/i18n/data/ps.py
turbogears/i18n/data/ps_AF.py
turbogears/i18n/data/pt.py
turbogears/i18n/data/pt_BR.py
turbogears/i18n/data/pt_PT.py
turbogears/i18n/data/ro.py
turbogears/i18n/data/ro_RO.py
turbogears/i18n/data/ru.py
turbogears/i18n/data/ru_RU.py
turbogears/i18n/data/ru_UA.py
turbogears/i18n/data/sh.py
turbogears/i18n/data/sh_YU.py
turbogears/i18n/data/sk.py
turbogears/i18n/data/sk_SK.py
turbogears/i18n/data/sl.py
turbogears/i18n/data/sl_SI.py
turbogears/i18n/data/so.py
turbogears/i18n/data/so_DJ.py
turbogears/i18n/data/so_ET.py
turbogears/i18n/data/so_KE.py
turbogears/i18n/data/so_SO.py
turbogears/i18n/data/sq.py
turbogears/i18n/data/sq_AL.py
turbogears/i18n/data/sr.py
turbogears/i18n/data/sr_YU.py
turbogears/i18n/data/sv.py
turbogears/i18n/data/sv_FI.py
turbogears/i18n/data/sv_SE.py
turbogears/i18n/data/sw.py
turbogears/i18n/data/sw_KE.py
turbogears/i18n/data/sw_TZ.py
turbogears/i18n/data/ta.py
turbogears/i18n/data/ta_IN.py
turbogears/i18n/data/te.py
turbogears/i18n/data/te_IN.py
turbogears/i18n/data/th.py
turbogears/i18n/data/th_TH.py
turbogears/i18n/data/ti.py
turbogears/i18n/data/ti_ER.py
turbogears/i18n/data/ti_ET.py
turbogears/i18n/data/tr.py
turbogears/i18n/data/tr_TR.py
turbogears/i18n/data/uk.py
turbogears/i18n/data/uk_UA.py
turbogears/i18n/data/vi.py
turbogears/i18n/data/vi_VN.py
turbogears/i18n/data/zh.py
turbogears/i18n/data/zh_CN.py
turbogears/i18n/data/zh_HK.py
turbogears/i18n/data/zh_MO.py
turbogears/i18n/data/zh_SG.py
turbogears/i18n/data/zh_TW.py
turbogears/i18n/data/de/LC_MESSAGES/TurboGears.mo
turbogears/i18n/data/de/LC_MESSAGES/TurboGears.po
turbogears/i18n/data/sl/LC_MESSAGES/TurboGears.mo
turbogears/i18n/data/sl/LC_MESSAGES/TurboGears.po
turbogears/i18n/sogettext/__init__.py
turbogears/i18n/sogettext/model.py
turbogears/i18n/tests/__init__.py
turbogears/i18n/tests/test_format.py
turbogears/i18n/tests/test_kidutils.py
turbogears/i18n/tests/test_so_gettext.py
turbogears/i18n/tests/test_tg_gettext.py
turbogears/i18n/tests/test_utils.py
turbogears/identity/__init__.py
turbogears/identity/conditions.py
turbogears/identity/exceptions.py
turbogears/identity/saprovider.py
turbogears/identity/soprovider.py
turbogears/identity/visitor.py
turbogears/identity/tests/__init__.py
turbogears/identity/tests/test_identity.py
turbogears/identity/tests/test_visit.py
turbogears/qstemplates/__init__.py
turbogears/qstemplates/qsbase/+einame+.egg-info/PKG-INFO
turbogears/qstemplates/qsbase/+einame+.egg-info/paster_plugins.txt
turbogears/qstemplates/qsbase/+einame+.egg-info/sqlobject.txt_tmpl
turbogears/qstemplates/qsbase/+package+/__init__.py
turbogears/qstemplates/qsbase/+package+/release.py_tmpl
turbogears/qstemplates/qsbase/+package+/static/css/empty_tmpl
turbogears/qstemplates/qsbase/+package+/static/images/favicon.ico
turbogears/qstemplates/qsbase/+package+/static/images/tg_under_the_hood.png
turbogears/qstemplates/qsbase/+package+/static/images/under_the_hood_blue.png
turbogears/qstemplates/qsbase/+package+/static/javascript/empty_tmpl
turbogears/qstemplates/qsbase/+package+/templates/__init__.py
turbogears/qstemplates/quickstart/README.txt_tmpl
turbogears/qstemplates/quickstart/dev.cfg_tmpl
turbogears/qstemplates/quickstart/sample-prod.cfg_tmpl
turbogears/qstemplates/quickstart/setup.py_tmpl
turbogears/qstemplates/quickstart/start-+package+.py_tmpl
turbogears/qstemplates/quickstart/test.cfg_tmpl
turbogears/qstemplates/quickstart/+package+/commands.py_tmpl
turbogears/qstemplates/quickstart/+package+/controllers.py_tmpl
turbogears/qstemplates/quickstart/+package+/json.py_tmpl
turbogears/qstemplates/quickstart/+package+/model.py_tmpl
turbogears/qstemplates/quickstart/+package+/config/__init__.py
turbogears/qstemplates/quickstart/+package+/config/app.cfg_tmpl
turbogears/qstemplates/quickstart/+package+/config/log.cfg_tmpl
turbogears/qstemplates/quickstart/+package+/static/css/style.css
turbogears/qstemplates/quickstart/+package+/static/images/header_inner.png
turbogears/qstemplates/quickstart/+package+/static/images/info.png
turbogears/qstemplates/quickstart/+package+/static/images/ok.png
turbogears/qstemplates/quickstart/+package+/templates/login.kid
turbogears/qstemplates/quickstart/+package+/templates/master.kid
turbogears/qstemplates/quickstart/+package+/templates/welcome.kid
turbogears/qstemplates/quickstart/+package+/tests/__init__.py
turbogears/qstemplates/quickstart/+package+/tests/test_controllers.py_tmpl
turbogears/qstemplates/quickstart/+package+/tests/test_model.py_tmpl
turbogears/qstemplates/quickstartbig/+package+/controllers/__init__.py_tmpl
turbogears/qstemplates/quickstartbig/+package+/controllers/root.py_tmpl
turbogears/qstemplates/widget/README.txt_tmpl
turbogears/qstemplates/widget/setup.py_tmpl
turbogears/qstemplates/widget/+package+/widgets.py_tmpl
turbogears/static/__init__.py
turbogears/static/css/__init__.py
turbogears/static/css/interpreter.css
turbogears/static/css/toolbox.css
turbogears/static/css/widget.css
turbogears/static/images/__init__.py
turbogears/static/images/add.png
turbogears/static/images/admi18n.png
turbogears/static/images/arrow_down.png
turbogears/static/images/arrow_down_small.png
turbogears/static/images/arrow_left.png
turbogears/static/images/arrow_right.png
turbogears/static/images/arrow_up.png
turbogears/static/images/arrow_up_small.png
turbogears/static/images/catwalk.png
turbogears/static/images/catwalk_logo.png
turbogears/static/images/catwalk_logo_bg.png
turbogears/static/images/column_chooser.png
turbogears/static/images/designer.png
turbogears/static/images/discard.png
turbogears/static/images/edit.png
turbogears/static/images/file.png
turbogears/static/images/folder.png
turbogears/static/images/identity.png
turbogears/static/images/info.png
turbogears/static/images/play.png
turbogears/static/images/remove.png
turbogears/static/images/save.png
turbogears/static/images/shell.png
turbogears/static/images/small_gear.png
turbogears/static/images/stop.png
turbogears/static/images/table.gif
turbogears/static/images/tg_power.png
turbogears/static/images/tg_under_the_hood.png
turbogears/static/images/tg_under_the_hood_sans_gear.png
turbogears/static/images/toolbox_logo.png
turbogears/static/images/toolbox_top_baggrund.png
turbogears/static/images/toolbox_top_vertical_line.png
turbogears/static/images/top.png
turbogears/static/images/top_bg.png
turbogears/static/images/transp.png
turbogears/static/images/trash.png
turbogears/static/images/widgets.png
turbogears/static/js/MochiKit.js
turbogears/static/js/__init__.py
turbogears/static/js/i18n_base.js
turbogears/static/js/interpreter.js
turbogears/static/js/widget.js
turbogears/static/js/tool-man/cookies.js
turbogears/static/js/tool-man/coordinates.js
turbogears/static/js/tool-man/core.js
turbogears/static/js/tool-man/css.js
turbogears/static/js/tool-man/drag.js
turbogears/static/js/tool-man/dragsort.js
turbogears/static/js/tool-man/events.js
turbogears/tests/__init__.py
turbogears/tests/config.cfg
turbogears/tests/configfile.cfg
turbogears/tests/form.kid
turbogears/tests/othertemplate.kid
turbogears/tests/paginate.kid
turbogears/tests/simple.kid
turbogears/tests/simplecheetah.tmpl
turbogears/tests/test_catwalk.py
turbogears/tests/test_command_i18n.py
turbogears/tests/test_config.py
turbogears/tests/test_controllers.py
turbogears/tests/test_database.py
turbogears/tests/test_decorator.py
turbogears/tests/test_errorhandling.py
turbogears/tests/test_expose.py
turbogears/tests/test_form_controllers.py
turbogears/tests/test_genericfunctions.py
turbogears/tests/test_paginate.py
turbogears/tests/test_sqlalchemy.py
turbogears/tests/test_testutil.py
turbogears/tests/test_validators.py
turbogears/tests/test_view.py
turbogears/tests/textfmt.tmpl
turbogears/tests/util.py
turbogears/tests/catwalk_models/__init__.py
turbogears/tests/catwalk_models/browse.py
turbogears/tests/catwalk_models/model_list.py
turbogears/tests/catwalk_models/model_structure.py
turbogears/tests/catwalk_models/single_join.py
turbogears/tests/catwalk_models/catwalk-session/session.pkl
turbogears/tests/locale/en/LC_MESSAGES/messages.mo
turbogears/tests/locale/en/LC_MESSAGES/messages.po
turbogears/tests/locale/fi/LC_MESSAGES/messages.mo
turbogears/tests/locale/fi/LC_MESSAGES/messages.po
turbogears/toolbox/__init__.py
turbogears/toolbox/base.py
turbogears/toolbox/console.kid
turbogears/toolbox/info.kid
turbogears/toolbox/main.kid
turbogears/toolbox/master.kid
turbogears/toolbox/shell.py
turbogears/toolbox/widgets.kid
turbogears/toolbox/admi18n/__init__.py
turbogears/toolbox/admi18n/catalog.py
turbogears/toolbox/admi18n/inter.kid
turbogears/toolbox/admi18n/internationalization.kid
turbogears/toolbox/admi18n/language.kid
turbogears/toolbox/admi18n/languageManagement.kid
turbogears/toolbox/admi18n/msgfmt.py
turbogears/toolbox/admi18n/po_view.kid
turbogears/toolbox/admi18n/pygettext.py
turbogears/toolbox/admi18n/stringCollection.kid
turbogears/toolbox/catwalk/__init__.py
turbogears/toolbox/catwalk/browse.py
turbogears/toolbox/catwalk/browse_grid.kid
turbogears/toolbox/catwalk/catwalk.kid
turbogears/toolbox/catwalk/columns.kid
turbogears/toolbox/catwalk/static/css/catwalk.css
turbogears/toolbox/catwalk/static/javascript/browse.js
turbogears/toolbox/catwalk/static/javascript/catwalk.js
turbogears/toolbox/catwalk/static/javascript/greybox/AmiJS.js
turbogears/toolbox/catwalk/static/javascript/greybox/blank.gif
turbogears/toolbox/catwalk/static/javascript/greybox/close.gif
turbogears/toolbox/catwalk/static/javascript/greybox/greybox.css
turbogears/toolbox/catwalk/static/javascript/greybox/greybox.js
turbogears/toolbox/catwalk/static/javascript/greybox/greybox__.js
turbogears/toolbox/catwalk/static/javascript/greybox/greybox_inline.js
turbogears/toolbox/catwalk/static/javascript/greybox/logo.png
turbogears/toolbox/catwalk/static/javascript/greybox/overlay.png
turbogears/toolbox/designer/__init__.py
turbogears/toolbox/designer/modelDesigner.kid
turbogears/toolbox/designer/static/css/style.css
turbogears/toolbox/designer/static/diagram/index.html
turbogears/toolbox/designer/static/diagram/images/back.gif
turbogears/toolbox/designer/static/diagram/images/shadow.png
turbogears/toolbox/designer/static/diagram/images/shadow_bottom.png
turbogears/toolbox/designer/static/diagram/images/shadow_corner.png
turbogears/toolbox/designer/static/diagram/images/shadow_right.png
turbogears/toolbox/designer/static/diagram/js/ajax.js
turbogears/toolbox/designer/static/diagram/js/animator.js
turbogears/toolbox/designer/static/diagram/js/generic.js
turbogears/toolbox/designer/static/diagram/js/io.js
turbogears/toolbox/designer/static/diagram/js/main.js
turbogears/toolbox/designer/static/diagram/js/objects.js
turbogears/toolbox/designer/static/diagram/js/settings.js
turbogears/toolbox/designer/static/diagram/js/sql_types.js
turbogears/toolbox/designer/static/diagram/styles/bar.css
turbogears/toolbox/designer/static/diagram/styles/foo
turbogears/toolbox/designer/static/diagram/styles/style.css
turbogears/toolbox/designer/static/diagram/styles/style.js
turbogears/toolbox/designer/static/images/info.png
turbogears/toolbox/designer/static/javascript/modelDesigner.js
turbogears/toolbox/designer/static/sessions/Survey.js
turbogears/toolbox/designer/static/sessions/TurboTunes.js
turbogears/toolbox/designer/static/sessions/WebShop.js
turbogears/view/__init__.py
turbogears/view/base.py
turbogears/view/templates/__init__.py
turbogears/view/templates/sitetemplate.kid
turbogears/visit/__init__.py
turbogears/visit/api.py
turbogears/visit/savisit.py
turbogears/visit/sovisit.py
turbogears/widgets/__init__.py
turbogears/widgets/base.py
turbogears/widgets/big_widgets.py
turbogears/widgets/datagrid.py
turbogears/widgets/forms.py
turbogears/widgets/i18n.py
turbogears/widgets/links.py
turbogears/widgets/meta.py
turbogears/widgets/rpc.py
turbogears/widgets/static/ajax.js
turbogears/widgets/static/ajaxgrid.js
turbogears/widgets/static/autocompletefield.css
turbogears/widgets/static/autocompletefield.js
turbogears/widgets/static/grid.css
turbogears/widgets/static/img.gif
turbogears/widgets/static/spinner.gif
turbogears/widgets/static/spinnerstopped.png
turbogears/widgets/static/calendar/calendar-blue.css
turbogears/widgets/static/calendar/calendar-blue2.css
turbogears/widgets/static/calendar/calendar-brown.css
turbogears/widgets/static/calendar/calendar-green.css
turbogears/widgets/static/calendar/calendar-setup.js
turbogears/widgets/static/calendar/calendar-system.css
turbogears/widgets/static/calendar/calendar-tas.css
turbogears/widgets/static/calendar/calendar-win2k-1.css
turbogears/widgets/static/calendar/calendar-win2k-2.css
turbogears/widgets/static/calendar/calendar-win2k-cold-1.css
turbogears/widgets/static/calendar/calendar-win2k-cold-2.css
turbogears/widgets/static/calendar/calendar.js
turbogears/widgets/static/calendar/menuarrow.gif
turbogears/widgets/static/calendar/menuarrow2.gif
turbogears/widgets/static/calendar/lang/calendar-af.js
turbogears/widgets/static/calendar/lang/calendar-al.js
turbogears/widgets/static/calendar/lang/calendar-bg.js
turbogears/widgets/static/calendar/lang/calendar-big5-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-big5-utf8.js
turbogears/widgets/static/calendar/lang/calendar-big5.js
turbogears/widgets/static/calendar/lang/calendar-br.js
turbogears/widgets/static/calendar/lang/calendar-ca.js
turbogears/widgets/static/calendar/lang/calendar-cs-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-cs-utf8.js
turbogears/widgets/static/calendar/lang/calendar-cs-win.js
turbogears/widgets/static/calendar/lang/calendar-da.js
turbogears/widgets/static/calendar/lang/calendar-de.js
turbogears/widgets/static/calendar/lang/calendar-du.js
turbogears/widgets/static/calendar/lang/calendar-el.js
turbogears/widgets/static/calendar/lang/calendar-en.js
turbogears/widgets/static/calendar/lang/calendar-es.js
turbogears/widgets/static/calendar/lang/calendar-fi.js
turbogears/widgets/static/calendar/lang/calendar-fr.js
turbogears/widgets/static/calendar/lang/calendar-he-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-he-utf8.js
turbogears/widgets/static/calendar/lang/calendar-hr-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-hr-utf8.js
turbogears/widgets/static/calendar/lang/calendar-hr.js
turbogears/widgets/static/calendar/lang/calendar-hu.js
turbogears/widgets/static/calendar/lang/calendar-it.js
turbogears/widgets/static/calendar/lang/calendar-jp.js
turbogears/widgets/static/calendar/lang/calendar-ko-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-ko-utf8.js
turbogears/widgets/static/calendar/lang/calendar-ko.js
turbogears/widgets/static/calendar/lang/calendar-lt-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-lt-utf8.js
turbogears/widgets/static/calendar/lang/calendar-lt.js
turbogears/widgets/static/calendar/lang/calendar-lv.js
turbogears/widgets/static/calendar/lang/calendar-nl.js
turbogears/widgets/static/calendar/lang/calendar-no.js
turbogears/widgets/static/calendar/lang/calendar-pl-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-pl-utf8.js
turbogears/widgets/static/calendar/lang/calendar-pl.js
turbogears/widgets/static/calendar/lang/calendar-pt-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-pt-utf8.js
turbogears/widgets/static/calendar/lang/calendar-pt.js
turbogears/widgets/static/calendar/lang/calendar-ro.js
turbogears/widgets/static/calendar/lang/calendar-ru.js
turbogears/widgets/static/calendar/lang/calendar-ru_win_.js
turbogears/widgets/static/calendar/lang/calendar-si.js
turbogears/widgets/static/calendar/lang/calendar-sk.js
turbogears/widgets/static/calendar/lang/calendar-sp.js
turbogears/widgets/static/calendar/lang/calendar-sv.js
turbogears/widgets/static/calendar/lang/calendar-tr.js
turbogears/widgets/static/calendar/lang/calendar-zh-cn-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-zh-cn-utf8.js
turbogears/widgets/static/calendar/lang/calendar-zh-cn.js
turbogears/widgets/static/calendar/lang/calendar-zh-tw-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-zh-tw-utf8.js
turbogears/widgets/static/calendar/lang/calendar-zh-tw.js
turbogears/widgets/static/calendar/lang/calendar-zh-utf-8.js
turbogears/widgets/static/calendar/lang/calendar-zh-utf8.js
turbogears/widgets/static/calendar/lang/calendar-zh.js
turbogears/widgets/static/calendar/skins/aqua/active-bg.gif
turbogears/widgets/static/calendar/skins/aqua/dark-bg.gif
turbogears/widgets/static/calendar/skins/aqua/hover-bg.gif
turbogears/widgets/static/calendar/skins/aqua/menuarrow.gif
turbogears/widgets/static/calendar/skins/aqua/normal-bg.gif
turbogears/widgets/static/calendar/skins/aqua/rowhover-bg.gif
turbogears/widgets/static/calendar/skins/aqua/status-bg.gif
turbogears/widgets/static/calendar/skins/aqua/theme.css
turbogears/widgets/static/calendar/skins/aqua/title-bg.gif
turbogears/widgets/static/calendar/skins/aqua/today-bg.gif
turbogears/widgets/static/sh/SyntaxHighlighter.css
turbogears/widgets/static/sh/shBrushCSharp.js
turbogears/widgets/static/sh/shBrushCss.js
turbogears/widgets/static/sh/shBrushDelphi.js
turbogears/widgets/static/sh/shBrushJScript.js
turbogears/widgets/static/sh/shBrushJava.js
turbogears/widgets/static/sh/shBrushPhp.js
turbogears/widgets/static/sh/shBrushPython.js
turbogears/widgets/static/sh/shBrushRuby.js
turbogears/widgets/static/sh/shBrushSql.js
turbogears/widgets/static/sh/shBrushVb.js
turbogears/widgets/static/sh/shBrushXml.js
turbogears/widgets/static/sh/shCore.js
turbogears/widgets/static/tabber/tabber-minimized.js
turbogears/widgets/static/tabber/tabber.css
turbogears/widgets/static/tabber/tabber.js
turbogears/widgets/static/tabber/tabber_cookie.js
turbogears/widgets/templates/__init__.py
turbogears/widgets/templates/datagrid.kid
turbogears/widgets/templates/paginate_datagrid.kid
turbogears/widgets/tests/__init__.py
turbogears/widgets/tests/form.kid
turbogears/widgets/tests/test_datagrid.py
turbogears/widgets/tests/test_forms.py
turbogears/widgets/tests/test_link_inclusion.py
turbogears/widgets/tests/test_nested_form_controllers.py
turbogears/widgets/tests/test_nested_widgets.py
turbogears/widgets/tests/test_new_validation.py
turbogears/widgets/tests/test_request_related_features.py
turbogears/widgets/tests/test_widgets.py
turbogears/widgets/tests/two_forms.kid
turbogears/widgets/tests/widget.kid
TurboGears-1.0.4.2/TurboGears.egg-info/not-zip-safe 0000644 0001750 0001750 00000000001 10710636776 020645 0 ustar faide faide
TurboGears-1.0.4.2/TurboGears.egg-info/top_level.txt 0000644 0001750 0001750 00000000013 10745210075 021127 0 ustar faide faide turbogears
TurboGears-1.0.4.2/TurboGears.egg-info/entry_points.txt 0000644 0001750 0001750 00000003020 10745210075 021674 0 ustar faide faide
[console_scripts]
tg-admin = turbogears.command:main
[distutils.commands]
docs = turbogears.docgen:GenSite
[paste.paster_create_template]
tgbase = turbogears.command.quickstart:BaseTemplate
turbogears = turbogears.command.quickstart:TurbogearsTemplate
tgbig = turbogears.command.quickstart:TGBig
tgwidget = turbogears.command.quickstart:TGWidgetTemplate
[turbogears.command]
quickstart = turbogears.command.quickstart:quickstart
sql = turbogears.command.base:SQL
shell = turbogears.command.base:Shell
toolbox = turbogears.command.base:ToolboxCommand
update = turbogears.command.quickstart:update
i18n = turbogears.command.i18n:InternationalizationTool
info = turbogears.command.info:InfoCommand
[turbogears.identity.provider]
sqlobject = turbogears.identity.soprovider:SqlObjectIdentityProvider
sqlalchemy= turbogears.identity.saprovider:SqlAlchemyIdentityProvider
[turbogears.extensions]
identity = turbogears.identity.visitor
visit = turbogears.visit
[turbogears.visit.manager]
sqlobject = turbogears.visit.sovisit:SqlObjectVisitManager
sqlalchemy = turbogears.visit.savisit:SqlAlchemyVisitManager
[turbogears.toolboxcommand]
widgets = turbogears.toolbox.base:WidgetBrowser
shell = turbogears.toolbox.shell:WebConsole
admi18n = turbogears.toolbox.admi18n:Internationalization
designer = turbogears.toolbox.designer:Designer
info = turbogears.toolbox.base:Info
catwalk = turbogears.toolbox.catwalk:CatWalk
TurboGears-1.0.4.2/TurboGears.egg-info/requires.txt 0000644 0001750 0001750 00000000726 10745210075 021010 0 ustar faide faide CherryPy >= 2.3.0,<3.0.0alpha
ConfigObj >= 4.3.2
DecoratorTools >= 1.4
FormEncode >= 0.7.1
PasteScript >= 0.9.7,<1.6
RuleDispatch >= 0.5a0.dev-r2303
setuptools >= 0.6c2
simplejson >= 1.3
TurboCheetah >= 1.0
TurboJson >= 1.1.2
TurboKid >= 1.0.4
[sqlobject]
SQLObject>=0.8,<0.10dev
[sqlalchemy]
SQLAlchemy >= 0.3.10
[testtools]
nose >= 0.9.3,<=0.10.0a1
[future]
Elixir>=0.4.0
Genshi>=0.4.4
[exp]
TGFastData
[tgtesttools]
nose >= 0.9.3,<=0.10.0a1
SQLAlchemy >= 0.3.10 TurboGears-1.0.4.2/TurboGears.egg-info/PKG-INFO 0000644 0001750 0001750 00000003532 10745210075 017503 0 ustar faide faide Metadata-Version: 1.0
Name: TurboGears
Version: 1.0.4.2
Summary: front-to-back rapid web development
Home-page: http://www.turbogears.org
Author: Kevin Dangoor
Author-email: dangoor+turbogears@gmail.com
License: MIT
Download-URL: http://www.turbogears.org/download/filelist.html
Description: Front-to-back rapid web development
===================================
TurboGears brings together four major pieces to create an
easy to install, easy to use web megaframework. It covers
everything from front end (MochiKit JavaScript for the browser,
Kid/Genshi/Mako/Cheetah for templates in Python) to the controllers
(CherryPy) to the back end (SQLAlchemy or SQLObject).
The TurboGears project is focused on providing documentation
and integration with these tools without losing touch
with the communities that already exist around those tools.
TurboGears is easy to use for a wide range of web applications.
The latest development version is available in the
`TurboGears subversion repository`_.
Our `mailing list`_ is lively and helpful, don't hesitate to
send your questions there, we will try to help you find out
a solution to your problem.
.. _mailing list:
http://groups.google.com/group/turbogears
.. _TurboGears subversion repository:
http://svn.turbogears.org/trunk#egg=turbogears-dev
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
TurboGears-1.0.4.2/TurboGears.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000042 10745210075 022456 0 ustar faide faide http://files.turbogears.org/eggs/
TurboGears-1.0.4.2/setup.cfg 0000644 0001750 0001750 00000000256 10745210077 014462 0 ustar faide faide [easy_install]
find_links = http://www.turbogears.org/download/index.html http://peak.telecommunity.com/snapshots
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
TurboGears-1.0.4.2/CONTRIBUTORS.txt 0000644 0001750 0001750 00000002477 10710636656 015355 0 ustar faide faide TurboGears was founded by Kevin Dangoor. Included projects were
created by Ian Bicking, Remi Delon, Bob Ippolito, Ryan Tomayko and
Fredrik Lundh. Many other people have contributed to the included
projects.
Other TurboGears contributors:
* Elvelind Grandin
* Ronald Jaramillo
* Jeff Watkins
* Dan Jacob
* Max Ischenko
* Simon Belak
* Karl Guertin
* Jared Kuolt
* David Stanek
* Richard Standbrook
* Michele Cella
* Alberto Valverde
* Lee McFadden
* Jorge Godoy
* Ondrej Zara (author of WWW SQL Designer)
* Irmen de Jong (author of Kronos)
* Florent Aide
* Travis Bradshaw
* Mark Ramm
* Felix Schwarz
* Christopher Arndt
* Chris Miles
* Diez B. Roggisch
* Paul Johnston
* Roger Demetrescu
* Christoph Zwerschke
Contributor Statement:
People who contribute non-trivial changes to TurboGears or have
commit access to the repository agree to the following statement:
I, __(your name here)__ agree to the following statements concerning
my contributions to the TurboGears project:
* These contributions are my own work and that I have full ownership
authority over them
* My contributions will be licensed under the same terms as TurboGears
itself (an "MIT-style" license)
* I am unaware of any patent or patent application that would prevent
free use of my contribution
Full legal name and address:
(your name and address here)
TurboGears-1.0.4.2/turbogears/ 0000755 0001750 0001750 00000000000 10745210077 015013 5 ustar faide faide TurboGears-1.0.4.2/turbogears/config.py 0000644 0001750 0001750 00000017075 10710636656 016653 0 ustar faide faide import sys, os, glob, re
from cherrypy import config
from configobj import ConfigObj
import pkg_resources
import logging
import logging.handlers
__all__ = ["update_config", "get", "update"]
try:
set
except NameError:
from sets import Set as set
class ConfigError(Exception):
pass
def _get_formatters(formatters):
for key, formatter in formatters.items():
kw = {}
fmt = formatter.get("format", None)
if fmt:
fmt = fmt.replace("*(", "%(")
kw["fmt"] = fmt
datefmt = formatter.get("datefmt", None)
if datefmt:
kw["datefmt"] = datefmt
formatter = logging.Formatter(**kw)
formatters[key] = formatter
def _get_handlers(handlers, formatters):
for key, handler in handlers.items():
kw = {}
try:
cls = handler.get("class")
args = handler.get("args", tuple())
level = handler.get("level", None)
try:
cls = eval(cls, logging.__dict__)
except NameError:
try:
cls = eval(cls, logging.handlers.__dict__)
except NameError, err:
raise ConfigError("Specified class in handler "
"%s is not a recognizable logger name" % key)
try:
handler_obj = cls(*eval(args, logging.__dict__))
except IOError,err:
raise ConfigError("Missing or wrong argument to "
"%s in handler %s -> %s " % (cls.__name__,key,err))
except TypeError,err:
raise ConfigError("Wrong format for arguments "
"to %s in handler %s -> %s" % (cls.__name__,key,err))
if level:
level = eval(level, logging.__dict__)
handler_obj.setLevel(level)
except KeyError:
raise ConfigError("No class specified for logging "
"handler %s" % key)
formatter = handler.get("formatter", None)
if formatter:
try:
formatter = formatters[formatter]
except KeyError:
raise ConfigError("Handler %s references unknown "
"formatter %s" % (key, formatter))
handler_obj.setFormatter(formatter)
handlers[key] = handler_obj
def _get_loggers(loggers, handlers):
for key, logger in loggers.items():
qualname = logger.get("qualname", None)
if qualname:
log = logging.getLogger(qualname)
else:
log = logging.getLogger()
level = logger.get("level", None)
if level:
level = eval(level, logging.__dict__)
else:
level = logging.NOTSET
log.setLevel(level)
propagate = logger.get("propagate", None)
if propagate is not None:
log.propagate = propagate
cfghandlers = logger.get("handlers", None)
if cfghandlers:
if isinstance(cfghandlers, basestring):
cfghandlers = [cfghandlers]
for handler in cfghandlers:
try:
handler = handlers[handler]
except KeyError:
raise ConfigError("Logger %s references unknown "
"handler %s" % (key, handler))
log.addHandler(handler)
def configure_loggers(config):
"""Configures the Python logging module, using options that are very
similar to the ones listed in the Python documentation. This also
removes the logging configuration from the configuration dictionary
because CherryPy doesn't like it there. Here are some of the Python
examples converted to the format used here:
[logging]
[[loggers]]
[[[parser]]]
[logger_parser]
level="DEBUG"
handlers="hand01"
propagate=1
qualname="compiler.parser"
[[handlers]]
[[[hand01]]]
class="StreamHandler"
level="NOTSET"
formatter="form01"
args="(sys.stdout,)"
[[formatters]]
[[[form01]]]
format="F1 *(asctime)s *(levelname)s *(message)s"
datefmt=
One notable format difference is that *() is used in the formatter
instead of %() because %() is already used for config file
interpolation.
"""
if not config.has_key("logging"):
config["global"]["tg.new_style_logging"] = False
return
logcfg = config["logging"]
formatters = logcfg.get("formatters", {})
_get_formatters(formatters)
handlers = logcfg.get("handlers", {})
_get_handlers(handlers, formatters)
loggers = logcfg.get("loggers", {})
_get_loggers(loggers, handlers)
del config["logging"]
config["global"]["tg.new_style_logging"] = True
def config_defaults():
current_dir_uri = os.path.abspath(os.getcwd())
if not current_dir_uri.startswith("/"):
current_dir_uri = "/" + current_dir_uri
defaults = {'current_dir_uri' : current_dir_uri}
return defaults
def config_obj(configfile = None, modulename = None):
defaults = config_defaults()
if modulename:
mod_globals = dict()
lastdot = modulename.rfind(".")
firstdot = modulename.find(".")
packagename = modulename[:lastdot]
top_level_package = modulename[:firstdot]
modname = modulename[lastdot+1:]
modfile = pkg_resources.resource_filename(packagename,
modname + ".cfg")
if not os.path.exists(modfile):
modfile = pkg_resources.resource_filename(packagename,
modname)
if os.path.isdir(modfile):
configfiles = glob.glob(os.path.join(modfile, "*.cfg"))
else:
configfiles = [modfile]
configdata = ConfigObj(unrepr=True)
top_level_dir = pkg_resources.resource_filename(
top_level_package, "")[:-1].replace("\\", "/")
package_dir = pkg_resources.resource_filename(
packagename, "")[:-1].replace("\\", "/")
defaults.update(dict(top_level_dir=top_level_dir,
package_dir=package_dir))
configdata.merge(dict(DEFAULT=defaults))
for file in configfiles:
configdata2 = ConfigObj(file, unrepr=True)
configdata2.merge(dict(DEFAULT=defaults))
configdata.merge(configdata2)
if configfile:
if modulename:
configdata2 = ConfigObj(configfile, unrepr=True)
configdata2.merge(dict(DEFAULT=defaults))
configdata.merge(configdata2)
else:
configdata = ConfigObj(configfile, unrepr=True)
return configdata
def update_config(configfile=None, modulename=None):
"""Updates the system configuration either from a ConfigObj
(INI-style) config file, a module name specified in dotted notation
or both (the module name is assumed to have a ".cfg" extension).
If both are specified, the module is called first,
followed by the config file. This means that the config file's options
override the options in the module file."""
configdict = config_obj(configfile, modulename).dict()
configure_loggers(configdict)
config.update(configdict)
def get(key, default_value=None, return_section=False, path=None):
"""Retrieves a config value"""
value = config.get(key, default_value, return_section, path)
if value and key == 'sqlobject.dburi' and os.name == "nt":
value = re.sub('///(\w):', '///\\1|', value)
return value
def update(configvalues):
"""Updates the configuration with the values from the dictionary."""
return config.update(configvalues)
TurboGears-1.0.4.2/turbogears/startup.py 0000644 0001750 0001750 00000025165 10724562302 017076 0 ustar faide faide "Things to do when TurboGears is imported."
import os
import errno
import logging
import sys
import time
import atexit
import signal
import pkg_resources
import cherrypy
from cherrypy import _cputil
from formencode.variabledecode import NestedVariables
from cherrypy._cpwsgi import wsgiApp, CPHTTPRequest
from cherrypy._cpwsgiserver import CherryPyWSGIServer
from turbogears import config, scheduler, database
from turbogears import view
from turbogears.database import hub_registry, EndTransactionsFilter
log = logging.getLogger("turbogears.startup")
pkg_resources.require("TurboGears")
def reloader_thread(freq):
"""Monkeypatch for the reloader provided by CherryPy.
This reloader is designed to reload a single package. This is
more efficient and, more important, compatible with zipped
libraries that may not provide access to the individual files."""
def archive_selector(module):
if hasattr(module, '__loader__'):
if hasattr(module.__loader__, 'archive'):
return module.__loader__.archive
return module
mtimes = {}
package = config.get("autoreload.package", None)
if package is None:
print \
"""TurboGears requires autoreload.package to be set. It can be an empty
value, which will use CherryPy's default behavior which is to check
every module. Setting an actual package makes the check much faster."""
return
while cherrypy.lib.autoreload.RUN_RELOADER:
if package:
modnames = filter(lambda modname: modname.startswith(package),
sys.modules.keys())
modlist = [sys.modules[modname] for modname in modnames]
else:
modlist = map(archive_selector, sys.modules.values())
for filename in filter(lambda v: v,
map(lambda m: getattr(m, "__file__", None), modlist)):
if filename.endswith(".kid") or filename == "<string>":
continue
orig_filename = filename
if filename.endswith(".pyc"):
filename = filename[:-1]
try:
mtime = os.stat(filename).st_mtime
except OSError, e:
if orig_filename.endswith('.pyc') and e[0] == errno.ENOENT:
# This prevents us from endlessly restarting
# if there is an old .pyc lying around
# after a .py file has been deleted
try: os.unlink(orig_filename)
except: pass
sys.exit(3) # force reload
if filename not in mtimes:
mtimes[filename] = mtime
continue
if mtime > mtimes[filename]:
sys.exit(3) # force reload
time.sleep(freq)
cherrypy.lib.autoreload.reloader_thread = reloader_thread
webpath = ""
DNS_SD_PID = None
def start_bonjour(package=None):
global DNS_SD_PID
if DNS_SD_PID:
return
if (not hasattr(cherrypy, "root")) or (not cherrypy.root):
return
if not package:
package = cherrypy.root.__module__
package = package[:package.find(".")]
host = config.get('server.socket_host', '')
port = str(config.get('server.socket_port'))
env = config.get('server.environment')
name = package + ": " + env
type = "_http._tcp"
cmds = [['/usr/bin/avahi-publish-service', ["-H", host, name, type, port]],
['/usr/bin/dns-sd', ['-R', name, type, "."+host, port, "path=/"]]]
for cmd, args in cmds:
# TODO:. This check is flawed. If one has both services installed and
# avahi isn't the one running, then this won't work. We should either
# try registering with both or checking what service is running and use
# that. Program availability on the filesystem was never enough...
if os.path.exists(cmd):
DNS_SD_PID = os.spawnv(os.P_NOWAIT, cmd, [cmd]+args)
atexit.register(stop_bonjour)
break
def stop_bonjour():
if not DNS_SD_PID:
return
try:
os.kill(DNS_SD_PID, signal.SIGTERM)
except OSError:
pass
class VirtualPathFilter(object):
"""Filter that makes CherryPy ignorant of a URL root path.
That is, you can mount your app so the URI "/users/~rdel/myapp/"
maps to the root object "/".
"""
def on_start_resource(self):
prefix = config.get('server.webpath', False)
if prefix:
path = cherrypy.request.object_path
if path == prefix:
cherrypy.request.object_path = '/'
elif path.startswith(prefix):
cherrypy.request.object_path = path[len(prefix):]
else:
raise cherrypy.NotFound(path)
class NestedVariablesFilter(object):
def before_main(self):
if hasattr(cherrypy.request, "params"):
cherrypy.request.params = \
NestedVariables.to_python(cherrypy.request.params or {})
def startTurboGears():
"""Handles TurboGears tasks when the CherryPy server starts.
This adds the "tg_js" configuration to make MochiKit accessible.
It also turns on stdlib logging when in development mode.
"""
config.update({"/tg_static":
{
"static_filter.on": True,
"static_filter.dir":
os.path.abspath(pkg_resources.resource_filename(__name__, "static")),
'log_debug_info_filter.on' : False,
}
})
config.update({"/tg_js" :
{
"static_filter.on" : True,
"static_filter.dir" :
os.path.abspath(pkg_resources.resource_filename(__name__, "static/js")),
'log_debug_info_filter.on' : False,
}
})
cherrypy.config.environments['development']['log_debug_info_filter.on'] = False
if config.get("decoding_filter.on", path="/") is None:
config.update({"/": {
"decoding_filter.on" : True,
"decoding_filter.encoding" : config.get(
"kid.encoding", "utf8")
}})
view.load_engines()
view.loadBaseTemplates()
global webpath
webpath = config.get("server.webpath", "")
if hasattr(cherrypy, "root") and cherrypy.root:
if not hasattr(cherrypy.root, "_cp_filters"):
cherrypy.root._cp_filters= []
morefilters = [EndTransactionsFilter(),
NestedVariablesFilter()]
if webpath:
morefilters.insert(0, VirtualPathFilter())
cherrypy.root._cp_filters.extend(morefilters)
if webpath.startswith("/"):
webpath = webpath[1:]
if webpath and not webpath.endswith("/"):
webpath = webpath + "/"
isdev = config.get('server.environment') == 'development'
if not config.get("tg.new_style_logging"):
if config.get('server.log_to_screen'):
setuplog = logging.getLogger()
setuplog.setLevel(logging.DEBUG)
fmt = logging.Formatter("%(asctime)s %(name)s "
"%(levelname)s %(message)s")
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)
handler.setFormatter(fmt)
setuplog.addHandler(handler)
logfile = config.get("server.log_file")
if logfile:
setuplog = logging.getLogger("turbogears.access")
setuplog.propagate = 0
fmt = logging.Formatter("%(message)s")
handler = logging.FileHandler(logfile)
handler.setLevel(logging.INFO)
handler.setFormatter(fmt)
setuplog.addHandler(handler)
bonjoursetting = config.get("tg.bonjour", None)
if bonjoursetting or isdev:
start_bonjour(bonjoursetting)
if config.get("sqlalchemy.dburi"):
database.get_engine()
# Start all TurboGears extensions
extensions = pkg_resources.iter_entry_points("turbogears.extensions")
for entrypoint in extensions:
ext = entrypoint.load()
if hasattr(ext, "start_extension"):
ext.start_extension()
for item in call_on_startup:
item()
if config.get("tg.scheduler", False):
scheduler._start_scheduler()
log.info("Scheduler started")
def stopTurboGears():
# end all transactions and clear out the hubs to
# help ensure proper reloading in autoreload situations
for hub in hub_registry:
hub.end()
hub_registry.clear()
stop_bonjour()
# Shut down all TurboGears extensions
extensions= pkg_resources.iter_entry_points( "turbogears.extensions" )
for entrypoint in extensions:
ext= entrypoint.load()
if hasattr(ext, "shutdown_extension"):
ext.shutdown_extension()
for item in call_on_shutdown:
item()
if config.get("tg.scheduler", False):
scheduler._stop_scheduler()
log.info("Scheduler stopped")
old_object_trail = _cputil.get_object_trail
# hang on to object trail to use it to find an app root if need be
def get_object_trail(object_path=None):
trail = old_object_trail(object_path)
try:
cherrypy.request.object_trail = trail
except AttributeError:
pass
return trail
_cputil.get_object_trail = get_object_trail
class SimpleWSGIServer(CherryPyWSGIServer):
"""A WSGI server that accepts a WSGI application as a parameter."""
RequestHandlerClass = CPHTTPRequest
def __init__(self):
conf = cherrypy.config.get
wsgi_app = wsgiApp
if conf('server.environment') == 'development':
try:
from paste.evalexception.middleware import EvalException
except ImportError:
pass
else:
wsgi_app = EvalException(wsgi_app, global_conf={})
cherrypy.config.update({'server.throw_errors':True})
bind_addr = (conf("server.socket_host"), conf("server.socket_port"))
CherryPyWSGIServer.__init__(self, bind_addr, wsgi_app,
conf("server.thread_pool"),
conf("server.socket_host"),
request_queue_size = conf(
"server.socket_queue_size"),
)
def start_server(root):
cherrypy.root = root
if config.get("tg.fancy_exception", False):
cherrypy.server.start(server=SimpleWSGIServer())
else:
cherrypy.server.start()
if startTurboGears not in cherrypy.server.on_start_server_list:
cherrypy.server.on_start_server_list.append(startTurboGears)
if stopTurboGears not in cherrypy.server.on_stop_server_list:
cherrypy.server.on_stop_server_list.append(stopTurboGears)
call_on_startup = []
call_on_shutdown = []
TurboGears-1.0.4.2/turbogears/genericfunctions.py 0000644 0001750 0001750 00000004705 10710636656 020747 0 ustar faide faide import sys
from itertools import izip, repeat, chain as ichain
from dispatch import strategy, functions
class MultiorderGenericFunction(functions.GenericFunction):
"""Generic function allowing a priori method ordering."""
def __init__(self, func):
functions.GenericFunction.__init__(self, func)
self.order_when = []
self.order_around = []
def when(self, cond, order=0):
if order not in self.order_when:
self.order_when.append(order)
self.order_when.sort()
return self._decorate(cond, "primary%d" % order)
def around(self, cond, order=0):
if order not in self.order_around:
self.order_around.append(order)
self.order_around.sort()
return self._decorate(cond, "around%d" % order)
# Based on dispatch.functions.GenericFunction.combine
def combine(self, cases):
strict = [strategy.ordered_signatures,strategy.safe_methods]
loose = [strategy.ordered_signatures,strategy.all_methods]
primary_names = ['primary%d' % order for order in self.order_when]
around_names = ['around%d' % order for order in self.order_around]
cases = strategy.separate_qualifiers(
cases,
before = loose, after =loose,
**dict(izip(ichain(primary_names, around_names), repeat(strict)))
)
primary = strategy.method_chain(ichain(
*[cases.get(primary, []) for primary in primary_names]))
if cases.get('after') or cases.get('before'):
befores = strategy.method_list(cases.get('before',[]))
afters = strategy.method_list(list(cases.get('after',[]))[::-1])
def chain(*args,**kw):
for tmp in befores(*args,**kw): pass # toss return values
result = primary(*args,**kw)
for tmp in afters(*args,**kw): pass # toss return values
return result
else:
chain = primary
if (self.order_around):
chain = strategy.method_chain(ichain(*([cases.get(around, [])
for around in around_names] + [[chain]])))
return chain
def getter(var):
"""Create an accessor for given variable."""
frame = sys._getframe(1)
return lambda: var in frame.f_locals and frame.f_locals[var] or \
frame.f_globals[var]
__all__ = ["MultiorderGenericFunction", "getter", ]
TurboGears-1.0.4.2/turbogears/controllers.py 0000644 0001750 0001750 00000054110 10724562302 017732 0 ustar faide faide """Classes and methods for TurboGears controllers."""
import logging
import re
import urllib
import types
from itertools import izip
import cherrypy
from dispatch import generic, strategy, functions
import turbogears.util as tg_util
import turbogears
from inspect import isclass
from turbogears import view, database, errorhandling, config
from turbogears.decorator import weak_signature_decorator
from turbogears.validators import Invalid
from turbogears.errorhandling import error_handler, exception_handler
log = logging.getLogger("turbogears.controllers")
unicodechars = re.compile(r"([^\x00-\x7F])")
if config.get("session_filter.on", None) == True:
if config.get("session_filter.storage_type", None) == "PostgreSQL":
import psycopg2
config.update(
{'session_filter.get_db': psycopg2.connect(
psycopg2.get('sessions.postgres.dsn'))
})
# support for mysql/sqlite/etc here
def _process_output(output, template, format, content_type,
mapping, fragment=False):
"""
Produces final output form from the data returned from a
controller method.
See the expose() arguments for more info in theses ones since
they are the same.
"""
if isinstance(output, dict):
from turbogears.widgets import js_location
css = tg_util.setlike()
js = dict(izip(js_location, iter(tg_util.setlike, None)))
include_widgets = {}
include_widgets_lst = config.get("tg.include_widgets", [])
if config.get("tg.mochikit_all", False):
include_widgets_lst.insert(0, 'turbogears.mochikit')
for i in include_widgets_lst:
widget = tg_util.load_class(i)
if isclass(widget):
widget = widget()
include_widgets["tg_%s" % i.split(".")[-1]] = widget
for script in widget.retrieve_javascript():
if hasattr(script, "location"):
js[script.location].add(script)
else:
js[js_location.head].add(script)
css.add_all(widget.retrieve_css())
for value in output.itervalues():
if hasattr(value, "retrieve_css"):
retrieve = getattr(value, "retrieve_css")
if callable(retrieve):
css.add_all(value.retrieve_css())
if hasattr(value, "retrieve_javascript"):
retrieve = getattr(value, "retrieve_javascript")
if callable(retrieve):
for script in value.retrieve_javascript():
if hasattr(script, "location"):
js[script.location].add(script)
else:
js[js_location.head].add(script)
output.update(include_widgets)
output["tg_css"] = css
#output.update([("tg_js_%s" % str(l), js[l]) for l in js_location])
for l in iter(js_location):
output["tg_js_%s" % str(l)] = js[l]
tg_flash = _get_flash()
if not tg_flash == None:
output["tg_flash"] = tg_flash
output = view.render(output, template=template, format=format,
mapping=mapping, content_type=content_type,
fragment=fragment)
else:
if content_type:
cherrypy.response.headers["Content-Type"] = content_type
# fix the Safari XMLHttpRequest encoding problem
try:
contentType = cherrypy.response.headers["Content-Type"]
ua = cherrypy.request.headers["User-Agent"]
except KeyError:
return output
if not contentType.startswith("text/"):
return output
ua = view.UserAgent(ua)
enc = tg_util.get_template_encoding_default()
if ua.browser == "safari":
if isinstance(output, str):
output = output.decode(enc)
elif isinstance(output, types.GeneratorType):
output = "".join(output)
output = unicodechars.sub(
lambda m: "&#x%x;" % ord(m.group(1)), output).encode("ascii")
if isinstance(output, unicode):
output = output.encode(enc)
return output
class BadFormatError(Exception):
"""Output-format exception."""
def validate(form=None, validators=None,
failsafe_schema=errorhandling.FailsafeSchema.none,
failsafe_values=None, state_factory=None):
"""Validate input.
@param form form to validate input from
@param validators individual validators to use for parameters
@param failsafe_schema fail-safe schema
@param failsafe_values replacements for erroneous inputs
@param state_factory callable which returns the initial state instance for
validation
"""
def entangle(func):
recursion_guard = dict(func=func)
if callable(form) and not hasattr(form, "validate"):
init_form = lambda self: form(self)
else:
init_form = lambda self: form
def validate(func, *args, **kw):
if tg_util.call_on_stack("validate", recursion_guard, 4):
return func(*args, **kw)
form = init_form(args and args[0] or kw["self"])
args, kw = tg_util.to_kw(func, args, kw)
errors = {}
if state_factory is not None:
state = state_factory()
else:
state = None
if form:
value = kw.copy()
try:
kw.update(form.validate(value, state))
except Invalid, e:
errors = e.unpack_errors()
cherrypy.request.validation_exception = e
cherrypy.request.validated_form = form
if validators:
if isinstance(validators, dict):
for field, validator in validators.iteritems():
try:
kw[field] = validator.to_python(
kw.get(field, None), state
)
except Invalid, error:
errors[field] = error
else:
try:
value = kw.copy()
kw.update(validators.to_python(value, state))
except Invalid, e:
errors = e.unpack_errors()
cherrypy.request.validation_exception = e
cherrypy.request.validation_errors = errors
cherrypy.request.input_values = kw.copy()
cherrypy.request.validation_state = state
if errors:
kw = errorhandling.dispatch_failsafe(failsafe_schema,
failsafe_values, errors, func, kw)
args, kw = tg_util.from_kw(func, args, kw)
return errorhandling.run_with_errors(errors, func, *args, **kw)
return validate
return weak_signature_decorator(entangle)
class CustomDispatch(functions.GenericFunction):
def combine(self,cases):
strict = [strategy.ordered_signatures,strategy.safe_methods]
cases = strategy.separate_qualifiers(
cases,
primary = strict,
)
primary = strategy.method_chain(cases.get('primary',[]))
if type(primary) != types.FunctionType:
for i in primary:
for y in i:
return y[1]
return primary
def _add_rule(_expose, found_default, as_format, accept_format, template,
rulefunc):
if as_format == "default":
if found_default:
colon = template.find(":")
if colon == -1:
as_format = template
else:
as_format = template[:colon]
else:
found_default = True
ruleparts = []
ruleparts.append('kw.get("tg_format", "default") == "%s"'
% as_format)
if accept_format:
ruleparts.append('(accept == "%s" and kw.get("tg_format", '
'"default") == "default")' % accept_format)
rule = " or ".join(ruleparts)
log.debug("Generated rule %s", rule)
_expose.when(rule)(rulefunc)
return found_default
def _build_rules(func):
def _expose(func, accept, allow_json, *args, **kw):
pass
_expose = generic(CustomDispatch)(_expose)
if func._allow_json:
log.debug("Adding allow_json rule: "
'allow_json and '
'(kw.get("tg_format", None) == "json" or accept '
'=="text/javascript")')
_expose.when('allow_json '
'and (kw.get("tg_format", None) == "json" or accept'
' =="text/javascript")')(
lambda _func, accept, allow_json,
*args, **kw: _execute_func(
_func, "json", None, None, None, False, args, kw))
found_default = False
for ruleinfo in func._ruleinfo:
found_default = _add_rule(_expose, found_default, **ruleinfo)
func._expose = _expose
def expose(template=None, validators=None, allow_json=None, html=None,
format=None, content_type=None, inputform=None, fragment=False,
as_format="default", mapping=None, accept_format=None):
"""Exposes a method to the web.
By putting the expose decorator on a method, you tell TurboGears that
the method should be accessible via URL traversal. Additionally, expose
handles the output processing (turning a dictionary into finished
output) and is also responsible for ensuring that the request is
wrapped in a database transaction.
You can apply multiple expose decorators to a method, if
you'd like to support multiple output formats. The decorator that's
listed first in your code without as_format or accept_format is
the default that is chosen when no format is specifically asked for.
Any other expose calls that are missing as_format and accept_format
will have as_format implicitly set to the whatever comes before
the ":" in the template name (or the whole template name if there
is no ":". For example, <code>expose("json")</code>, if it's not
the default expose, will have as_format set to "json".
When as_format is set, passing the same value in the tg_format
parameter in a request will choose the options for that expose
decorator. Similarly, accept_format will watch for matching
Accept headers. You can also use both. expose("json", as_format="json",
accept_format="text/javascript") will choose JSON output for either
case: tg_format=json as a parameter or Accept: text/javascript as a
request header.
Passing allow_json=True to an expose decorator
is equivalent to adding the decorator just mentioned.
Each expose decorator has its own set of options, and each one
can choose a different template or even template engine (you can
use Kid for HTML output and Cheetah for plain text, for example).
See the other expose parameters below to learn about the options
you can pass to the template engine.
Take a look at the
<a href="tests/test_expose-source.html">test_expose.py</a> suite
for more examples.
@param template "templateengine:dotted.reference" reference along the
Python path for the template and the template engine. For
example, "kid:foo.bar" will have Kid render the bar template in
the foo package.
@keyparam format format for the template engine to output (if the
template engine can render different formats. Kid, for example,
can render "html", "xml" or "xhtml")
@keyparam content_type sets the content-type http header
@keyparam allow_json allow the function to be exposed as json
@keyparam fragment for template engines (like Kid) that generate
DOCTYPE declarations and the like, this is a signal to
just generate the immediate template fragment. Use this
if you're building up a page from multiple templates or
going to put something onto a page with .innerHTML.
@keyparam mapping mapping with options that are sent to the template
engine
@keyparam as_format designates which value of tg_format will choose
this expose.
@keyparam accept_format which value of an Accept: header will
choose this expose.
@keyparam html deprecated in favor of template
@keyparam validators deprecated. Maps argument names to validator
applied to that arg
@keyparam inputform deprecated. A form object that generates the
input to this method
"""
if html:
template = html
if not template:
template = format
if format == "json" or (format == None and template == None):
template = "json"
allow_json = True
if content_type is None:
content_type = config.get("tg.content_type", None)
if config.get("tg.session.automatic_lock",None) == True:
cherrypy.session.acquire_lock()
def entangle(func):
log.debug("Exposing %s", func)
log.debug("template: %s, format: %s, allow_json: %s, "
"content-type: %s", template, format, allow_json, content_type)
if not getattr(func, "exposed", False):
def expose(func, *args, **kw):
accept = cherrypy.request.headers.get('Accept', "").lower()
if not hasattr(func, "_expose"):
_build_rules(func)
if hasattr(cherrypy.request, "in_transaction"):
output = func._expose(func, accept, func._allow_json,
*args, **kw)
else:
cherrypy.request.in_transaction = True
output = database.run_with_transaction(
func._expose, func, accept, func._allow_json,
*args, **kw)
return output
func.exposed = True
func._ruleinfo = []
allow_json_from_config = config.get(
"tg.allow_json", False)
func._allow_json = allow_json_from_config
else:
expose = lambda func, *args, **kw: func(*args, **kw)
func._ruleinfo.insert(0, dict(as_format = as_format,
accept_format = accept_format, template = template,
rulefunc = lambda _func, accept, allow_json,
*args, **kw:
_execute_func(_func, template, format, content_type,
mapping, fragment, args, kw)))
if allow_json:
func._allow_json = True
if inputform or validators:
import warnings
warnings.warn(
"Use a separate decorator validate() rather than passing "
"arguments validators and/or inputform to decorator "
"expose().",
DeprecationWarning, 2)
func = validate(form=inputform, validators=validators)(func)
return expose
return weak_signature_decorator(entangle)
def _execute_func(func, template, format, content_type, mapping, fragment, args, kw):
"""Call controller method and process it's output."""
if config.get("tg.strict_parameters", False):
tg_util.remove_keys(kw, ["tg_random", "tg_format"])
else:
args, kw = tg_util.adapt_call(func, args, kw)
if config.get('server.environment', 'development') == 'development':
# Only output this in development mode: If it's a field storage object,
# this means big memory usage, and we don't want that in production
log.debug("Calling %s with *(%s), **(%s)", func, args, kw)
output = errorhandling.try_call(func, *args, **kw)
if isinstance(output, list):
return output
assert isinstance(output, basestring) or isinstance(output, dict) \
or isinstance(output, types.GeneratorType), \
"Method %s.%s() returned unexpected output. Output should " \
"be of type basestring, dict or generator." % (
args[0].__class__.__name__, func.__name__)
if isinstance(output, dict):
template = output.pop("tg_template", template)
format= output.pop("tg_format", format)
if template and template.startswith("."):
template = func.__module__[:func.__module__.rfind('.')]+template
return _process_output(output, template, format, content_type, mapping, fragment)
def flash(message):
"""Set a message to be displayed in the browser on next page display."""
cherrypy.response.simple_cookie['tg_flash'] = tg_util.to_utf8(message)
cherrypy.response.simple_cookie['tg_flash']['path'] = '/'
def _get_flash():
"""Retrieve the flash message (if one is set), clearing the message."""
request_cookie = cherrypy.request.simple_cookie
response_cookie = cherrypy.response.simple_cookie
def clearcookie():
response_cookie["tg_flash"] = ""
response_cookie["tg_flash"]['expires'] = 0
response_cookie['tg_flash']['path'] = '/'
if response_cookie.has_key("tg_flash"):
message = response_cookie["tg_flash"].value
response_cookie.pop("tg_flash")
if request_cookie.has_key("tg_flash"):
# New flash overrided old one sitting in cookie. Clear that old cookie.
clearcookie()
elif request_cookie.has_key("tg_flash"):
message = request_cookie["tg_flash"].value
if not response_cookie.has_key("tg_flash"):
clearcookie()
else:
message = None
if message:
message = unicode(message, 'utf-8')
return message
class Controller(object):
"""Base class for a web application's controller.
Currently, this provides positional parameters functionality
via a standard default method.
"""
class RootController(Controller):
"""Base class for the root of a web application.
Your web application should have one of these. The root of
your application is used to compute URLs used by your app.
"""
is_app_root = True
msglog = logging.getLogger('cherrypy.msg')
msglogfunc = {0: msglog.info, 1: msglog.warning, 2: msglog.error}
def _cp_log_message(self, msg, context = 'nocontext', severity = 0):
log = self.msglogfunc[severity]
text = ''.join((context, ': ', msg))
log(text)
accesslog = logging.getLogger("turbogears.access")
def _cp_log_access(self):
tmpl = '%(h)s %(l)s %(u)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'
try:
username = cherrypy.request.user_name
if not username:
username = "-"
except AttributeError:
username = "-"
s = tmpl % {'h': cherrypy.request.remote_host,
'l': '-',
'u': username,
'r': cherrypy.request.requestLine,
's': cherrypy.response.status.split(" ", 1)[0],
'b': cherrypy.response.headers.get('Content-Length',
'') or "-",
'f': cherrypy.request.headers.get('referer', ''),
'a': cherrypy.request.headers.get('user-agent', ''),
}
self.accesslog.info(s)
Root = RootController
def url(tgpath, tgparams=None, **kw):
"""Computes URLs.
tgpath can be a list or a string. If the path is absolute (starts
with a "/"), the server.webpath and the approot of the application
are prepended to the path. In order for the approot to be
detected properly, the root object should extend
controllers.RootController.
Query parameters for the URL can be passed in as a dictionary in
the second argument *or* as keyword parameters.
Values which are a list or a tuple are used to create multiple
key-value pairs.
"""
if not isinstance(tgpath, basestring):
tgpath = "/".join(list(tgpath))
if tgpath.startswith("/"):
if tg_util.request_available():
check_app_root()
tgpath = cherrypy.request.app_root + tgpath
result = config.get("server.webpath", "") + tgpath
else:
result = tgpath
if tgparams is None:
tgparams = kw
else:
try:
tgparams = tgparams.copy()
tgparams.update(kw)
except AttributeError:
raise TypeError('url() expects a dictionary for query parameters')
args = []
for key, value in tgparams.iteritems():
if value is None:
continue
if isinstance(value, (list, tuple)):
pairs = [(key, v) for v in value]
else:
pairs = [(key, value)]
for (k,v) in pairs:
if v is None:
continue
if isinstance(v, unicode):
v = v.encode("utf8")
args.append("%s=%s" % (k, urllib.quote(str(v))))
if args:
result += "?" + "&".join(args)
return result
def check_app_root():
"""Sets cherrypy.request.app_root if needed."""
if hasattr(cherrypy.request, "app_root"):
return
found_root = False
trail = cherrypy.request.object_trail
top = len(trail) - 1
# compute the app_root by stepping back through the object
# trail and collecting up the path elements after the first
# root we find
# we can eliminate this if we find a way to use
# CherryPy's mounting mechanism whenever a new root
# is hit.
rootlist = []
for i in xrange(len(trail) - 1, -1, -1):
path, obj = trail[i]
if not found_root and isinstance(obj, RootController):
if i == top:
break
found_root = True
if found_root and i > 0:
rootlist.insert(0, path)
app_root = "/".join(rootlist)
if not app_root.startswith("/"):
app_root = "/" + app_root
if app_root.endswith("/"):
app_root = app_root[:-1]
cherrypy.request.app_root = app_root
def redirect(redirect_path, redirect_params=None, **kw):
"""
Redirect (via cherrypy.HTTPRedirect).
Raises the exception instead of returning it, this to allow
users to both call it as a function or to raise it as an exception.
"""
raise cherrypy.HTTPRedirect(
url(tgpath=redirect_path, tgparams=redirect_params, **kw))
__all__ = ["expose", "validate", "redirect", "flash",
"Root", "RootController", "Controller",
"error_handler", "exception_handler",
]
TurboGears-1.0.4.2/turbogears/paginate.py 0000644 0001750 0001750 00000046311 10742747733 017175 0 ustar faide faide import re
import types
from math import ceil
import logging
import warnings
import cherrypy
try:
import sqlobject
from sqlobject.main import SelectResults
except ImportError:
SelectResults = None
sqlobject = None
try:
# Can't depend on sqlalchemy being available.
import sqlalchemy
from sqlalchemy.ext.selectresults import SelectResults as SASelectResults
from sqlalchemy.orm.query import Query
# sqlalchemy 0.4.x turned SelectResults into a function that returns a Query
if isinstance(SASelectResults, types.FunctionType):
SASelectResults = None
except ImportError:
SASelectResults = None
sqlalchemy = None
Query = None
import turbogears
from turbogears.controllers import redirect
from turbogears.decorator import weak_signature_decorator
from turbogears.view import variable_providers
from formencode.variabledecode import variable_encode, variable_decode
log = logging.getLogger("turbogears.paginate")
# lists of databases that lack support for OFFSET
# this will need to be updated periodically as modules change
_so_no_offset = 'mssql maxdb sybase'.split()
_sa_no_offset = 'mssql maxdb access'.split()
# this is a global that is set the first time paginate() is called
_simulate_offset = None
def paginate(var_name, default_order='', default_reversed=None, limit=10,
allow_limit_override=False, max_pages=5, dynamic_limit=None):
'''
@param var_name: the variable name that the paginate decorator will try
to control. This key must be present in the dictionnary returned from
your controller in order for the paginate decorator to be able to handle
it.
@type var_name: string
@param default_order: Needs work! XXX
@type default_order: string or a list of strings. Any string starting with
"-" (dash) indicates a reverse order for that field/column.
@param default_reversed: Needs work! XXX
@type default_reversed: Boolean [Deprecated]
@param limit: the hard coded limit that the paginate decorator will
impose on the number of "var_name" to display at the same time.
This value can be overridden by the use of the dynamic_limit keyword
argument
@type limit: integer
@param allow_limit_override: A boolean that indicates if the parameters
passed in the calling URL can modify the imposed limit. By default it is
set to False. If you want to be able to control the limit by using an
URL parameter then you need to set this to True.
@type allow_limit_override: Boolean
@param max_pages: Needs work! XXX
@type max_pages: integer
@param dynamic_limit: If specified, this parameter must be the name
of a key present in the dictionnary returned by your decorated
controller. The value found for this key will be used as the limit
for our pagination and will override the other settings, the hard-coded
one declared in the decorator itself AND the URL parameter one.
This enables the programmer to store a limit settings inside the
application preferences and then let the user manage it.
@type dynamic_limit: string
'''
def entangle(func):
def decorated(func, *args, **kw):
def kwpop(default, *names):
for name in names:
if kw.has_key(name):
return kw.pop(name)
return default
if default_reversed is not None:
warnings.warn(
"default_reversed is deprecated. Use default_order='-field'"
" to indicate default reversed order or"
" default_order=['field1', '-field2, 'field3']"
" for multiple fields", DeprecationWarning, 2)
get = turbogears.config.get
page = kwpop(1, var_name + '_tgp_no', 'tg_paginate_no')
if page == 'last':
page = None
else:
page = int(page)
if page < 1:
page = 1
if get('paginate.redirect_on_out_of_range'):
cherrypy.request.params[var_name + '_tgp_no'] = page
redirect(cherrypy.request.path, cherrypy.request.params)
limit_ = int(
kwpop(limit, var_name + '_tgp_limit', 'tg_paginate_limit'))
order = kwpop(None, var_name + '_tgp_order', 'tg_paginate_order')
ordering = kwpop(
{},
var_name + '_tgp_ordering',
'tg_paginate_ordering')
# Convert ordering str to a dict.
if ordering:
ordering = convert_ordering(ordering)
if not allow_limit_override:
limit_ = limit
log.debug("Pagination params: page=%s, limit=%s, order=%s "
"", page, limit_, order)
# get the output from the decorated function
output = func(*args, **kw)
if not isinstance(output, dict):
return output
try:
var_data = output[var_name]
except KeyError:
raise StandardError("Didn't get expected variable")
if dynamic_limit:
try:
dyn_limit = output[dynamic_limit]
except KeyError:
msg = "dynamic_limit: %s not found in output dict" % (
dynamic_limit)
raise StandardError(msg)
limit_ = dyn_limit
if order and not default_order:
msg = "If you want to enable ordering you need "
msg += "to provide a default_order"
raise StandardError(msg)
elif default_order and not ordering:
if isinstance(default_order, basestring):
# adapt old style to new style
df = [(default_reversed and "-" or "") + default_order]
elif default_reversed:
raise StandardError("default_reversed (deprecated) is only "
" allowed when default_order is basestring type")
else:
df = default_order
ordering = dict([(v.lstrip('-'), [k, not v.startswith('-')])
for k,v in enumerate(df)])
elif ordering and order:
sort_ordering(ordering, order)
log.debug('ordering %s' % ordering)
row_count = 0
if (SelectResults and isinstance(var_data, SelectResults)) or \
(SASelectResults and isinstance(var_data, SASelectResults)) or \
(Query and isinstance(var_data, Query)):
row_count = var_data.count() or 0
if ordering:
# Build order_by list.
order_cols = range(len(ordering))
for (colname, order_opts) in ordering.items():
col = sql_get_column(colname, var_data)
if not col:
msg = "The order column (%s) doesn't exist" % colname
raise StandardError(msg)
order_by_expr = sql_order_col(col, order_opts[1])
order_cols[order_opts[0]] = order_by_expr
# May need to address potential of ordering already
# existing in var_data.
# SO and SA differ on this method name.
if hasattr(var_data, 'orderBy'):
var_data = var_data.orderBy(order_cols)
else:
var_data = var_data.order_by(order_cols)
elif isinstance(var_data, list) or (sqlalchemy and isinstance(
var_data, sqlalchemy.orm.attributes.InstrumentedList)):
row_count = len(var_data)
else:
raise StandardError(
'Variable is not a list or SelectResults or Query (%s)' % type(
var_data))
# If limit is zero then return all our rows
if not limit_:
limit_ = row_count or 1
page_count = int(ceil(float(row_count)/limit_))
if page > page_count:
page = max(page_count, 1)
if get('paginate.redirect_on_out_of_range'):
cherrypy.request.params[var_name + '_tgp_no'] = page
redirect(cherrypy.request.path, cherrypy.request.params)
if page is None:
page = max(page_count, 1)
if get('paginate.redirect_on_last_page'):
cherrypy.request.params[var_name + '_tgp_no'] = page
redirect(cherrypy.request.path, cherrypy.request.params)
offset = (page-1) * limit_
# if it's possible display every page
if page_count <= max_pages:
pages_to_show = range(1,page_count+1)
else:
pages_to_show = _select_pages_to_show(page_count=page_count,
current_page=page,
max_pages=max_pages)
# which one should we use? cherrypy.request.input_values or kw?
#input_values = cherrypy.request.input_values.copy()
##input_values = kw.copy()
input_values = variable_encode(cherrypy.request.params.copy())
input_values.pop('self', None)
for input_key in input_values.keys():
if input_key.startswith(var_name + '_tgp_') or \
input_key.startswith('tg_paginate'):
del input_values[input_key]
paginate_instance = Paginate(
current_page=page,
limit=limit_,
pages=pages_to_show,
page_count=page_count,
input_values=input_values,
order=order,
ordering=ordering,
row_count=row_count,
var_name=var_name)
cherrypy.request.paginate = paginate_instance
if not hasattr(cherrypy.request, 'paginates'):
cherrypy.request.paginates = dict()
cherrypy.request.paginates[var_name] = paginate_instance
# we replace the var with the sliced one
endpoint = offset + limit_
log.debug("slicing data between %d and %d", offset, endpoint)
global _simulate_offset
if _simulate_offset is None:
_simulate_offset = get('paginate.simulate_offset', None)
if _simulate_offset is None:
_simulate_offset = False
so_db = get('sqlobject.dburi', 'NOMATCH:').split(':', 1)[0]
sa_db = get('sqlalchemy.dburi', 'NOMATCH:').split(':', 1)[0]
if so_db in _so_no_offset or sa_db in _sa_no_offset:
_simulate_offset = True
log.warning("simulating OFFSET, paginate may be slow")
log.warning("to turn off, set "
"paginate.simulate_offset=False")
if _simulate_offset:
var_data_iter = iter(var_data[:endpoint])
# skip over the number of records specified by offset
for i in range(offset):
var_data_iter.next()
# return the records that remain
output[var_name] = list(var_data_iter)
else:
output[var_name] = var_data[offset:endpoint]
return output
return decorated
return weak_signature_decorator(entangle)
def _paginate_var_provider(d):
# replaced cherrypy.thread_data for cherrypy.request
# thanks alberto!
paginate = getattr(cherrypy.request, 'paginate', None)
if paginate:
d.update(dict(paginate=paginate))
paginates = getattr(cherrypy.request, 'paginates', None)
if paginates:
d.update(dict(paginates=paginates))
variable_providers.append(_paginate_var_provider)
class Paginate:
"""class for variable provider"""
def __init__(self, current_page, pages, page_count, input_values,
limit, order, ordering, row_count, var_name):
self.var_name = var_name
self.pages = pages
self.limit = limit
self.page_count = page_count
self.current_page = current_page
self.input_values = input_values
self.order = order
self.ordering = ordering
self.row_count = row_count
self.first_item = page_count and ((current_page - 1) * limit + 1) or 0
self.last_item = min(current_page * limit, row_count)
self.reversed = False
# Should reversed be true?
for (field_name, ordering_values) in ordering.items():
if ordering_values[0] == 0 and not ordering_values[1]:
self.reversed = True
# If ordering is empty, don't add it.
input_values = {var_name + '_tgp_limit': limit}
if ordering:
input_values[var_name + '_tgp_ordering'] = ordering
self.input_values.update(input_values)
if current_page < page_count:
self.input_values.update({
var_name + '_tgp_no': current_page + 1,
var_name + '_tgp_limit': limit
})
self.href_next = turbogears.url(
cherrypy.request.path,
self.input_values)
self.input_values.update({
var_name + '_tgp_no': 'last',
var_name + '_tgp_limit': limit
})
self.href_last = turbogears.url(
cherrypy.request.path,
self.input_values)
else:
self.href_next = None
self.href_last = None
if current_page > 1:
self.input_values.update({
var_name + '_tgp_no': current_page - 1,
var_name + '_tgp_limit': limit
})
self.href_prev = turbogears.url(
cherrypy.request.path,
self.input_values)
self.input_values.update({
var_name + '_tgp_no': 1,
var_name + '_tgp_limit': limit
})
self.href_first = turbogears.url(
cherrypy.request.path,
self.input_values)
else:
self.href_prev = None
self.href_first = None
def get_href(self, page, order=None, reverse_order=None):
# Note that reverse_order is not used. It should be cleaned up here
# and in the template. I'm not removing it now because I don't want
# to break the API.
order = order or None
input_values = self.input_values.copy()
input_values[self.var_name + '_tgp_no'] = page
if order:
input_values[ self.var_name + '_tgp_order'] = order
return turbogears.url('', input_values)
def _select_pages_to_show(current_page, page_count, max_pages):
pages_to_show = []
if max_pages < 3:
msg = "The minimun value for max_pages on this algorithm is 3"
raise StandardError(msg)
if page_count <= max_pages:
pages_to_show = range(1,page_count+1)
pad = 0
if not max_pages % 2:
pad = 1
start = current_page - (max_pages / 2) + pad
end = current_page + (max_pages / 2)
if start < 1:
end = end + (start * -1) + 1
start = 1
if end > page_count:
start = start - (end - page_count)
end = page_count
return range(start, end+1)
def sort_ordering(ordering, sort_name):
"""Rearrange ordering based on sort_name."""
log.debug('sort called with %s and %s' % (ordering, sort_name))
if ordering.setdefault(sort_name, [-1, True])[0] == 0:
# Flip
ordering[sort_name][1] = not ordering[sort_name][1]
else:
ordering[sort_name][0] = -1
# re-sort dictionary
items = ordering.items()
items.sort(lambda x,y: cmp(x[1],y[1]))
for i,v in enumerate(items):
ordering[v[0]][0] = i
log.debug('sort results is %s and %s' % (ordering, sort_name))
def sql_get_column(colname, var_data):
"""Return a column from var_data based on colname."""
if SelectResults and isinstance(var_data, SelectResults):
col = getattr(var_data.sourceClass.q, colname, None)
elif SASelectResults and isinstance(var_data, SASelectResults):
col = getattr(
var_data._query.mapper.c,
colname[len(var_data._query.mapper.column_prefix or ''):],
None)
elif Query and isinstance(var_data, Query):
col = getattr(
var_data.mapper.c,
colname[len(var_data.mapper.column_prefix or ''):],
None)
#if no attribute is found, let's try searching for 'foreign' objects...
# eg.: address.user.occupation.name
if not col and colname.find('.'):
seq = colname.split('.')
mapper = var_data.mapper
for propname in seq[:-1]:
prop = mapper.get_property(
propname, resolve_synonyms=True, raiseerr=False)
if not prop:
break
mapper = prop.mapper
# last item from split should be a simple attribute
col = getattr(
mapper.c,
seq[-1][len(mapper.column_prefix or ''):],
None)
else:
raise StandardError, 'expected SelectResults'
return col
def sql_order_col(col, ascending=True):
"""Return an ordered col for col."""
if sqlalchemy and isinstance(col, sqlalchemy.sql.ColumnElement):
if ascending:
order_col = sqlalchemy.sql.asc(col)
else:
order_col = sqlalchemy.sql.desc(col)
elif sqlobject and isinstance(col, types.InstanceType):
# I don't like using InstanceType, but that's what sqlobject col type
# is.
if ascending:
order_col = col
else:
order_col = sqlobject.DESC(col)
else:
raise StandardError, 'expected Column, but got %s' % str(type(col))
return order_col
# Ordering re:
ordering_expr = re.compile(r"('\w+(\.\w+)*'): ?\[(\d+), ?(True|False)\]")
def convert_ordering(ordering):
"""Covert ordering unicode string to dict."""
log.debug('ordering received %s' % str(ordering))
# eval would be simple, but insecure.
if not isinstance(ordering, (str, unicode)):
raise ValueError, "ordering should be string or unicode."
new_ordering = {}
if ordering == u"{}":
pass
else:
try:
ordering_info_find = ordering_expr.findall(ordering)
emsg = "Didn't match ordering for %s." % str(ordering)
assert len(ordering_info_find) > 0, emsg
for ordering_info in ordering_info_find:
ordering_key = str(ordering_info[0]).strip("'")
ordering_order = int(ordering_info[2])
ordering_reverse = bool(ordering_info[3] == 'True')
new_ordering[ordering_key] = [ordering_order,
ordering_reverse]
except StandardError, e:
log.debug('FAILED to convert ordering.')
new_ordering = {}
log.debug('ordering converted to %s' % str(new_ordering))
return new_ordering
TurboGears-1.0.4.2/turbogears/scheduler.py 0000644 0001750 0001750 00000042637 10710636656 017366 0 ustar faide faide """Module that provides a cron-like task scheduler.
This task scheduler is designed to be used from inside your own program.
You can schedule Python functions to be called at specific intervals or
days. It uses the standard 'sched' module for the actual task scheduling,
but provides much more:
- repeated tasks (at intervals, or on specific days)
- error handling (exceptions in tasks don't kill the scheduler)
- optional to run scheduler in its own thread or separate process
- optional to run a task in its own thread or separate process
If the threading module is available, you can use the various Threaded
variants of the scheduler and associated tasks. If threading is not
available, you could still use the forked variants. If fork is also
not available, all processing is done in a single process, sequentially.
There are three Scheduler classes:
Scheduler ThreadedScheduler ForkedScheduler
You usually add new tasks to a scheduler using the add_interval_task or
add_daytime_task methods, with the appropriate processmethod argument
to select sequential, threaded or forked processing. NOTE: it is impossible
to add new tasks to a ForkedScheduler, after the scheduler has been started!
For more control you could use one of the following Task classes
and use schedule_task or schedule_task_abs:
IntervalTask ThreadedIntervalTask ForkedIntervalTask
WeekdayTask ThreadedWeekdayTask ForkedWeekdayTask
MonthdayTask ThreadedMonthdayTask ForkedMonthdayTask
Kronos is the Greek God of Time.
This module is based on Kronos by Irmen de Jong, but has been modified
to better fit within TurboGears. Additionally, this module appeared to
no longer be supported/in development.
"""
#
# $Id: kronos.py,v 1.5 2004/10/06 22:43:49 irmen Exp $
#
# (c) Irmen de Jong.
# This is open-source software, released under the MIT Software License:
# http://www.opensource.org/licenses/mit-license.php
#
import os, sys
import sched, time
import traceback
import weakref
from turbogears.util import Enum
method = Enum("sequential", "forked", "threaded")
class Scheduler:
"""The Scheduler itself."""
def __init__(self):
self.running=True
self.sched = sched.scheduler(time.time, self.__delayfunc)
def __delayfunc(self, delay):
# This delay function is basically a time.sleep() that is
# divided up, so that we can check the self.running flag while delaying.
# there is an additional check in here to ensure that the top item of
# the queue hasn't changed
if delay<10:
time.sleep(delay)
else:
toptime = self.sched.queue[0][0]
endtime = time.time() + delay
period=5
stoptime = endtime - period
while self.running and stoptime > time.time() and \
self.sched.queue[0][0] == toptime:
time.sleep(period)
if not self.running or self.sched.queue[0][0] != toptime:
return
now = time.time()
if endtime > now:
time.sleep(endtime - now)
def _acquire_lock(self): pass
def _release_lock(self): pass
def add_interval_task(self, action, taskname, initialdelay, interval, processmethod, args, kw):
"""Add a new Interval Task to the schedule. A very short initialdelay or one of
zero cannot be honored, you will see a slight delay before the task is first
executed. This is because the scheduler needs to pick it up in its loop."""
if initialdelay<0 or interval<1:
raise ValueError("delay or interval must be >0")
# Select the correct IntervalTask class. Not all types may be available!
if processmethod==method.sequential:
TaskClass=IntervalTask
elif processmethod==method.threaded:
TaskClass = ThreadedIntervalTask
elif processmethod==method.forked:
TaskClass = ForkedIntervalTask
else:
raise ValueError("invalid processmethod")
if not args:
args=[]
if not kw:
kw={}
task = TaskClass(taskname, interval, action, args, kw)
self.schedule_task(task, initialdelay)
return task
def add_daytime_task(self, action, taskname, weekdays, monthdays, timeonday, processmethod, args, kw):
"""Add a new Day Task (Weekday or Monthday) to the schedule."""
if weekdays and monthdays:
raise ValueError("you can only specify weekdays or monthdays, not both")
if not args:
args=[]
if not kw:
kw={}
if weekdays:
# Select the correct WeekdayTask class. Not all types may be available!
if processmethod==method.sequential:
TaskClass=WeekdayTask
elif processmethod==method.threaded:
TaskClass = ThreadedWeekdayTask
elif processmethod==method.forked:
TaskClass = ForkedWeekdayTask
else:
raise ValueError("invalid processmethod")
task=TaskClass(taskname, weekdays, timeonday, action, args, kw)
if monthdays:
# Select the correct MonthdayTask class. Not all types may be available!
if processmethod==method.sequential:
TaskClass=MonthdayTask
elif processmethod==method.threaded:
TaskClass = ThreadedMonthdayTask
elif processmethod==method.forked:
TaskClass = ForkedMonthdayTask
else:
raise ValueError("invalid processmethod")
task=TaskClass(taskname, monthdays, timeonday, action, args, kw)
firsttime=task.get_schedule_time(True)
self.schedule_task_abs(task, firsttime)
return task
def schedule_task(self, task, delay):
"""Low-level method to add a new task to the scheduler with the given delay (seconds)."""
if self.running:
self._acquire_lock() # lock the sched queue, if needed
try:
task.event = self.sched.enter(delay, 0, task,
(weakref.ref(self),) )
finally:
self._release_lock()
else:
task.event = self.sched.enter(delay, 0, task,
(weakref.ref(self),) )
def schedule_task_abs(self, task, abstime):
"""Low-level method to add a new task to the scheduler for the given absolute time value."""
if self.running:
self._acquire_lock() # lock the sched queue, if needed
try:
task.event = self.sched.enterabs(abstime, 0, task,
(weakref.ref(self),) )
finally:
self._release_lock()
else:
task.event = self.sched.enterabs(abstime, 0, task,
(weakref.ref(self),) )
def start(self):
"""Start the scheduler."""
self._run()
def stop(self):
"""Remove all pending tasks and stop the Scheduler."""
self.running=False
self.sched.queue[:]=[]
def cancel(self, task):
self.sched.cancel(task.event)
def _run(self):
# Low-level run method to do the actual scheduling loop.
while self.running:
try:
self.sched.run()
except Exception,x:
print >>sys.stderr, "ERROR DURING SCHEDULER EXECUTION",x
print >>sys.stderr, "".join(traceback.format_exception(*sys.exc_info()))
print >>sys.stderr, "-"*20
# queue is empty; sleep a short while before checking again
if self.running:
time.sleep(5)
class Task:
"""Abstract base class of all scheduler tasks"""
def __init__(self, name, action, args, kw):
"""This is an abstract class!"""
self.name=name
self.action=action
self.args=args
self.kw=kw
def __call__(self, schedulerref):
"""Execute the task action in the scheduler's thread."""
try:
self.execute()
except Exception,x:
self.handle_exception(x)
self.reschedule(schedulerref())
def reschedule(self, scheduler):
"""This is an abstract class, this method is defined in one of the sub classes!"""
raise NotImplementedError("you're using the abstract base class 'Task', use a concrete class instead")
def execute(self):
"""Execute the actual task."""
self.action(*self.args, **self.kw)
def handle_exception(self, exc):
"""Handle any exception that occured during task execution."""
print >>sys.stderr, "ERROR DURING TASK EXECUTION",exc
print >>sys.stderr,"".join(traceback.format_exception(*sys.exc_info()))
print >>sys.stderr,"-"*20
class IntervalTask(Task):
"""A repeated task that occurs at certain intervals (in seconds)."""
def __init__(self, name, interval, action, args=None, kw=None):
Task.__init__(self, name, action, args, kw)
self.interval=interval
def reschedule(self, scheduler):
# reschedule this task according to its interval (in seconds).
scheduler.schedule_task(self, self.interval)
class DayTaskRescheduler:
"""A mixin class that contains the reschedule logic for the DayTasks."""
def __init__(self, timeonday):
self.timeonday=timeonday
def get_schedule_time(self, today):
"""Calculate the time value at which this task is to be scheduled."""
now=list(time.localtime())
if today:
# schedule for today. let's see if that is still possible
if (now[3], now[4]) >= self.timeonday:
now[2]+=1 # too bad, it will be tomorrow
else:
now[2]+=1 # tomorrow
now[3], now[4] = self.timeonday # set new time on day (hour,minute)
now[5]=0 # seconds
return time.mktime(now)
def reschedule(self, scheduler):
# Reschedule this task according to the daytime for the task.
# The task is scheduled for tomorrow, for the given daytime.
# (The execute method in the concrete Task classes will check
# if the current day is a day on which the task must run).
abstime = self.get_schedule_time(False)
scheduler.schedule_task_abs(self, abstime)
class WeekdayTask(DayTaskRescheduler, Task):
"""A task that is called at specific days in a week (1-7), at a fixed time on the day."""
def __init__(self, name, weekdays, timeonday, action, args=None, kw=None):
if type(timeonday) not in (list,tuple) or len(timeonday) != 2:
raise TypeError("timeonday must be a 2-tuple (hour,minute)")
if type(weekdays) not in (list,tuple):
raise TypeError("weekdays must be a sequence of weekday numbers 1-7 (1 is Monday)")
DayTaskRescheduler.__init__(self, timeonday)
Task.__init__(self, name, action, args, kw)
self.days=weekdays
def execute(self):
# This is called every day, at the correct time. We only need to
# check if we should run this task today (this day of the week).
weekday=time.localtime().tm_wday+1
if weekday in self.days:
self.action(*self.args, **self.kw)
class MonthdayTask(DayTaskRescheduler, Task):
"""A task that is called at specific days in a month (1-31), at a fixed time on the day."""
def __init__(self, name, monthdays, timeonday, action, args=None, kw=None):
if type(timeonday) not in (list,tuple) or len(timeonday) != 2:
raise TypeError("timeonday must be a 2-tuple (hour,minute)")
if type(monthdays) not in (list,tuple):
raise TypeError("monthdays must be a sequence of monthdays numbers 1-31")
DayTaskRescheduler.__init__(self, timeonday)
Task.__init__(self, name, action, args, kw)
self.days=monthdays
def execute(self):
# This is called every day, at the correct time. We only need to
# check if we should run this task today (this day of the month).
if time.localtime().tm_mday in self.days:
self.action(*self.args, **self.kw)
try:
import threading
class ThreadedScheduler(Scheduler):
"""A Scheduler that runs in its own thread."""
def __init__(self):
Scheduler.__init__(self)
self._lock=threading.Lock() # we require a lock around the task queue
def start(self):
# Start method that splices of a thread in which the scheduler will run.
self.thread=threading.Thread(target=self._run)
self.thread.setDaemon(True)
self.thread.start()
def stop(self):
# Stop method that stops the scheduler and waits for the thread to finish.
Scheduler.stop(self)
try:
self.thread.join()
except AttributeError:
pass
def _acquire_lock(self):
self._lock.acquire() # lock the thread's task queue
def _release_lock(self):