diff -Nru turbogears2-2.3.7/CHANGES.txt turbogears2-2.3.12/CHANGES.txt --- turbogears2-2.3.7/CHANGES.txt 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/CHANGES.txt 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,767 @@ += Change Log = + +== 2.3.12 (**April 6, 2018**) == + +* OPTIONS HTTP method for a RestController will now respond with a 204 "No Content" by default instead of a 404 +* WebOb version constrained to >=1.2,<1.8.0 to ensure full compatibility with TurboGears2.3 +* Fixed highlight of active page in Jinja quickstart + +== 2.3.11 (**July 3, 2017**) == + +* When configuring authentication it is now possible to provide ``('default', None)`` as an identifier to set custom identifiers and retain the default one. +* Python 3.6 is now officially supported +* Invalid requests (like GETs with a body) will not crash ``decode_params`` anymore. +* ``tg.util.webtest.test_context`` no longer depends on ``WebTest`` and can work on a plain ``TGApp`` instance. +* ErrorReporter now supports ``trace_errors.smtp_port`` option. +* ``trace_errors.enable = False`` can now disable error tracing middleware even when backlash is available +* Improved Kajiki template not found error message +* Support for datetime.time in json encoder +* Extract i18n from python code in kajiki templates by default +* Support for IPython in tgshell command. +* Add support for migrations from multiple databases when more than one database is involved. +* Fix for SCRIPT_NAME in login in quickstart template. + +== 2.3.10 (**December 4, 2016**) == + +* Fixed unicode error messages in ``validation_errors_response`` +* Added support for strip_text in kajiki templates +* Transaction manager no longer sets current path as not (fixes crash with transaction 2.0.3) +* Improved deprecated features warning reporting +* Allow custom error pages to only render for specific content types through ``errorpage.content_types`` option. +* Custom error pages no longer will render for content types != text/html +* Fixed ``tg.abort`` passthrough support, now properly skipping error pages and authentication. +* Changing ``tg.response.content_type`` in controller actions will now properly change the rendered template when multiple ones are registered. +* Documented request attributes and methods that previously were not. +* Exposed ``request.disable_error_pages()`` and ``request.disable_auth_challenger()`` to skip custom error pages and auth challenging in current request. +* It is no longer needed for ``lib.helpers`` and ``lib.app_globals`` to be imported into ``lib/__init__.py``. +* Controller Actions can now return ``None`` for a *204 No Content* response. +* Returning ``tg.response`` from a controller action will now return the response as it is instead of creating a new copy of it +* JSON renderer now accepts rendering only a part of the dictionary returned by the action through the ``key`` render_param. +* It is now possible to return JSON lists instead of objects from actions by using ``render_params={'allow_lists': False, 'key': 'values'}`` in ``@expose`` and returning the list in ``values`` key of the action returned dictionary. +* ``script_json_encode`` will now encode as JSON lists and iso dates by default. +* Remove downloads they are not more supported by pypi +* Fixed scaffolds for templates in newly quickstarted projects + +== 2.3.9 (**August 11, 2016**) == + +* Kajiki is now the default template engine for easier quickstarting of projects on Python 3.4 and 3.5 +* tgext.debugbar is now installed with devtools +* When quickstarting a project with Genshi on Python3.4 "name_constant" patch is enabled by default +* Devtools are now tested against Python 3.4 and 3.5 too +* Python 3.2 is no longer officially supported. +* Allow running python scripts through 'gearbox tgshell' +* Support render_params in kajiki +* Allow forcing file extension using !ext syntax in dotted templates +* Allow mixing dotted and file notations in mako renderer too +* Allow custom template file extension for mako templates +* Make DotteFileNameFinder work with zips too and add test for it +* Adapted tests for webob that doesn't specify encoding anymore for json +* Better support pkg_resources for forezen TG apps + +== 2.3.8 (**March 16, 2016**) == + +* Fix auth logger not being configured in Ming quickstart +* Add minimal quickstart option, to remove example pages in quickstart +* Serialize sessions as JSON in newly quickstarted projects +* error messages in newly quickstarted projects are not required to be valid XML anymore. +* Configuration steps now only read/write options from configuration instead of AppConfig +* Refactor configuration steps making them internal +* Allow init_model to return the initialized session to enable both ming and sqlalchemy. +* Directly expose milestones in main tg namespace +* Clearly express that TGApp only methods available to users are __call__ and classmethods +* Provide TGApp.lookup_controller class method to allow to lookup controllers based on a specific configuration +* All milestones are now properly triggered when load_environment is not provided +* In case load_environment is False reuse global config as a new one is not created. +* Remove built-in routes support, this can now be implemented through _dispatch and is provided in tgext.routes +* Rely on new crank flatten_arguments function, this avoid crashes when controller receives unexpected arguments +* thread_locals got renamed to context in most locations +* Dispatch now relies on crank 0.8 resolve api +* Take params from DispatchState, this allows dispatcher to modify them +* No longer switch tmpl_context strictness depending on debug/production, that lead to unexpected bugs on production +* raise stacklevel in warnings to provider a better suited context in warnings. +* Allow lists in JSON Encoder when explicitly permitted +* unless utility function, works well coped with Convert validation utility. +* Support for scheme argument in url, lurl and redirect +* database name cannot be an empty string in mongodb +* Make LazyString objects iterable + + +== 2.3.7 (**October 13, 2015**) == + +* Documentation of configuration options has been greatly improved and most of them are now explicitly documented. +* It's now far easier to configure Helpers, App Globals and Database in Minimal mode +* Newly quickstarted projects now provide a scaffold (gearbox scaffold) for Ming models +* Some improvements to @cached, inside the controller the caching namespace and key are now available as request.caching +* Kajiki templates by default now provide .xhtml extension +* Kajiki rendering mode is now switched to html5 by default. +* Kajiki templates are now properly added to newly quickstarted kajiki projects +* Fixed database cleanup in Test Units when using Ming +* tg.util now provides tg.util.sqlalchemy and tg.util.ming with utilities to convert models to dictionaries for quick forms initialization. +* Kajiki now provides html_optional_tags option which by default forces Kajiki to close html, head and body tags. +* Easier configuration of database and models in minimal mode +* Fixed i18n message extraction in newly quickstarted projects when not using Genshi +* get_partial_dict now reports the element name when it raises an AttributeError +* DottedFileNameFinder returns absolute path always, copes with some odd python envs +* Raise error when encoding detached SA instance to JSON +* AppGlobals and Helpers can now be configured even without using a package +* ToscaWidgets2 support for Kajiki is now enabled +* Ensure that exclude_names is always an iterable for registered templates +* When rendering ErrorController, reuse the existing response so that users can access custom attributes instead of creating a new one +* request.args_params will now contain both request params and positional dispatch arguments (excluding *args). This ensures that @cached uses all params to generate the cache key not only the GET and POST params +* AppConfig.get_root_module is now marked as an internal method and has been renamed accordingly +* Crank (TG routing system) has experimental support for Python 3.6 + +== 2.3.6 (**July 26, 2015**) == + +* Multiple @validate decorators can now be applied on same method. +* Identity Metadata provisioning (TGAuthMetadata) is now performed by an application wrapper so that it can rely on caching and other features +* Beaker is now optional and should be a dependency of the application, if it is not installed Session and Caching AppWrappers get disabled. +* All new quickstarted projects now expose Beaker and Babel as dependencies. +* tg.util.webtest.test_context now available for unit testing of features that depend from a TurboGears context ( http://turbogears.readthedocs.org/en/development/reference/classes.html#tg.util.webtest.test_context ) +* tg.validation.Convert utility function for simple validation that doesn't rely on formencode or tw2 ( http://turbogears.readthedocs.org/en/development/reference/classes.html#tg.validation.Convert ) +* Lazy strings are now supported by JSONEncoder, useful for i18n in JSON responses +* tg.util.dates module provides utilities when working with dates ( http://turbogears.readthedocs.org/en/development/reference/classes.html#tg.util.dates.get_fixed_timezone ) +* tg.util.files module provides utilities when working with files and paths ( http://turbogears.readthedocs.org/en/development/reference/classes.html#tg.util.files.DottedFileNameFinder ) +* flash.allow_html option permits to disable HTML escaping in flash messages ( http://turbogears.readthedocs.org/en/development/reference/classes.html#tg.flash.TGFlash.configure ) +* LazyString now supports all underlying string instance methods +* Kajiki templates now properly supports i18n +* Kajiki templates can now have a custom extension by using the templating.kajiki.template_extension option +* Kajiki templates can now disable automatic CDATA blocks using the templating.kajiki.cdata_scripts option +* Kajiki templates can now use autoblocks features using the templating.kajiki.xml_autoblocks option +* @expose can now replace an existing exposition for same content_type if old one was with no engine specified. +* Additional tests are now run against PY3 due to more libraries being available +* All new quickstarted projects now use WebHelpers2 package if available on system. + +== 2.3.5 (**April 28, 2015**) == + +* request.identity is now unprintable for security reasons. +* Hooks now support different separated namespaces to avoid collisions with third party apps +* Quickstart now has support for MongoDB based projects on Python3 +* Quickstart now provides .scaffold files for ``gearbox scaffold`` command. +* Quickstart login form now keeps username when login fails +* Configuration process cleanups, now .ini options overwrite AppConfig +* Various configuration options have been grouped under a common namespace (i18n.*, session.*, tm.*, errorpage.*) +* New ``configure_new_app`` hook that guarantees to always receive the unwrapped TGApp +* i18n, caching, session, transaction manager, ming and error pages are now application wrappers for speedups and better integration +* application wrappers now have a clear interface provided by ApplicationWrapper ABC +* Work-around for Genshi broken on Py3.4 +* Improved support for connecting to a MongoDB ReplicaSet +* request.path is no longer cached, so application wrappers can change it and redirect the request +* Let /_test_vars work even when debug is False when in test mode + +== 2.3.4 (**October 2, 2014**) == + +* Support for customizing Flash template +* Improving JSON support, permit customization without need for simplegeneric and support ISO dates +* Reference for configuration options is now automatically generate out of .configure methods of GlobalConfigurable objects +* Support the trace_slowreqs.enable config switch, this permits to actually turn off slow requests tracing. +* PyPy is now a test environment for TurboGears, each build is tested against it +* request.language is now deprecated as it was just a duplicate of tg.config['lang'] +* tg.abort can now be used as an error_handler to give an error response in case of validation failure +* tg.validation_errors_response can be used to quickly return a 412 response with errors in JSON format in case of validation falure +* Ensure that when 'lang' option is set and i18n is enabled lang is used as fallback language +* @decode_params decorator is now available to accept controller parameters from request json body +* Utility functions to login/logout users +* Avoid shadowing KeyError exceptions in hooks, previously KeyError during hooks were swallowed +* All functions in tg.util are now public and documented for use by TurboGears users. + +== 2.3.3 (**July 7, 2014**) == + +* Quickstart now uses HTTPFound instead of redirect to avoid appending SCRIPT_NAME multiple times +* Decoration.requirement is now a wrapper against first of Decoration.requirements +* Avoid Deprecation warnings on backlash +* Improving tg.render_template documentation +* Add support for backlash slow requests reporting +* Fix issue with app_globals when pylons compatibility mode is turned off +* Pagination and JSON encoding of Ming query cursors +* _visit now available with _before and _after, makes possible to implement custom behavior when the controller is visited for dispatching (before authorization) +* Pass through abort and SmartDenial improvements to authorization +* wrap and partial are built-in into python as we support only 2.6+ +* Support using LazyUrl as a location for HTTPFound +* Refactor @beaker_cache to remove dependency on decorator module +* @cached decorator which replaced @beaker_cache and decoupling controller wrappers from app configurator +* It is now possible to register multiple requirements per method, provide API and tests +* decorator not used anymore as @require is now a Decoration based decorator +* As we don't depend on repoze.what anymore, make @require use Decoration instead of behing a plain python decorator +* Store validation context in a Bunch, so that it can be accessed with dotted notation +* make possible to provide a context for @cached_property, useful to make it thread safe +* get_lang now always returns a list +* Fix wrong ordering in exposition inheritance when renderers are not ready yet + +== 2.3.2 (**March 8, 2014**) == + +* Quickstarted applications have been upgrade to Bootstrap3 CSS Framework +* Python3 support now includes TurboGears Admin +* Multiple Speedups in request dispatch and memory collection +* JSONP renderer is now provided built-in +* Renderers support has been rewrote, renderers are now easily pluginable +* Error reporting improvements: Reporting to Sentry is now built-in, when reporting exceptions by email it is now possible to attach request dump and local frame. Error options are now grouped in trace_errors namespace. +* Remove PasteDeploy dependency removed when in Minimal (microframework) mode. + +* Support simple custom templates for paginator +* Support for URL translations, it is now possible to serve as controllers urls that contain odd characters. +* Improve TW2 rendering engine detection +* Quickstarted applications now enable the debugbar if installed +* Improve error_handler support in validation, they can now keep decoration and wrappers +* AppConfig plain configuration methods got renamed to _configure_* convention +* TGMing dependency has been removed when quickstarting with MongoDB/Ming +* chameleon_genshi renderer support moved to an external extension +* Improved the JSON Encoder, generators are now resolved as lists and tg.lurl() result can be encoded. +* Fix validation error reporting for TW2 with nested layouts +* Login should now forward also url GET parameters when redirecting +* Improvements to i18n support major feature is the ability to make get_lang() return only the languages supported by the application instead of returning all languages requested by user. +* Trigger controller_wrappers resolution on environment_loaded milestone instead of config_ready so that they can be register on setup hook +* To be coherent with add_ming_middleware rename the sqlalchemy related middleware setup method to add_sqlalchemy_middleware + +== 2.3.1 (**November 4, 2013**) == + +* Also make error handling a class method as it is not bound to the controller instance +* Less usage of request local objects +* Keep track of method requirements in decoration +* Make _perform_validate a class method as it doesn't actually depend on the controller instance +* Skip coverage of a deprecated function that only forwards call to new API +* Improve registry manager streaming to cope with WSGI server that read the output like gevent-socketio +* A bit of move around of setup_tg_wsgi_app parts to cleanup roles of each component +* Put Decoration.register_hook in place again due to libraries using it but add a deprecation warning +* remove wrong TODO +* Refactor validation status reporting, tmpl_context.form_errors and tmpl_context.form_values got removed in favor of request.validation which keeps more details +* Fix docstring for AppConfig +* Refactoring TurboGears hooks and expose a public API to manage custom hooks +* Lazy resolution of application wrappers, so that ordering can be applied when they are all available +* Configuration milestones support, enables expositions to be resolved lazily when renderers are available + +== 2.X == + + * @with_trailing_slash and @without_trailing_slash have been moved to a 301 permanent redirect for SEO reasons + * FriendlyFormPlugin has been replaced by the builtin FastFormPlugin with Py3 support, to keep using FriendlyFormPlugin just build it and use the form_plugin option of sa_auth + * Removed support for "app:" prefix in template lookup, it actually didn't work much + * Removed tg.decorators.postpone_commits, wasn't documented and it's trivial to replicate for specific projects + * Remove tg.decorators.allow_only it actually didn't work and test for it was disabled. + +== 2.1b2: (**March 27, 2009**) == + + * Various fixes to dispatch involving _lookup + * Functools removed as a dependency of TG2 + * Fixed #2456 TurboJson dependency causing RuntimeWarning + * Fixed #2437 tg.url() docstring does not match behavior, pagination problem + * Fixed #2455 Admin list view fails with more than 8 records + * Fixed #1905 use variable_decode to add automagical formparsing like tg1 + * Fixed #2272 tg.url() is too laxist with argument types + * Fixed #2299 flash() doesn't display anything when message is too long + * Fixed #2337 RestController cannot handle non-ascii URLs + * Fixed #2358 override_template does not work if @without_trailing_slash placed before @expose) + * Fixed #2394 Configuration value "base_config.serve_static" sthould be set in *.ini files rather than in "app_cfg.py" file + * Fixed #2403 Provide a TGCommand base class within TurboGears 2.x core + * Fixed #2411 @https decorator crashes outside of root + * Fixed #2413 Installing tw.dojo breaks CrudRestController from tgext.crud + * Fixed #2416 Spurious SAWarnings caused by built-in SQL-based auth + * Fixed #2450 TG 2.1 tests failing with recent WebOb version + * Fixed #2451 Override template failing in 2.1 + * Fixed #2457 Make override_template work for all content types + * Fixed #2458 Allow custom REST-like methods in RestController enhancement + * Fixed #2469 Python2.4 issue when the uuid package is not installed + * Tests fixed to not be order-dependent. + * mako bytcode cashing added. + * Python 2.4 compatibility. + * Added tg.abort as a proxy to pylons.controllers.util.abort. + +== 2.1b1: (**January 25, 2009**) == + + * Deprecated default in favor of _default + * Fixed handling of Unicode parameters + * Added disable_request_extensions flag to configuration to allow users to ignore the request extension dispatch bits of object dispatch. + * Increased length of Permission.permission_name to 63 chars. + * Fixed GET requests on nested RestControllers. + * Fixed case-sensitive incorrectness in quickstart when using mako template option. + * Fixed numerous URL routing bugs, consilidating the RC and TGC controller base. + * Fixed eroneous tg.url call inside the quickstart template. + * Added use_dotted_templatenames support for Genshi. + * Added ignore_parameters setting in the config + * Added ability to have sa_auth.cookie_secret in .ini file + * Added cookie_secret to quickstart template + * Added tgext template + +== 2.1a3: (**November, 29 2009**) == + + * Fixed problems with Beaker Secret Key functionality. (Thanks Sanjiv) + * Fixed infinite loop problem associated with lookup. + * Fixed RestController so delete() works with nested RCs. + * Refactored Auth support to handle non-SA auth. + * Deprecated lookup in favor of _lookup. + +== 2.1a2: (**October, 25 2009**) == + + * Front-ported Beaker Secret key functionality. + * Fixed i18n hitting the filesystem on every call. + * Fixed #2287, allowing genshi to now output xhtml. + * Fixed #2273, allowing TW config from within the paste.ini file. + * Fixed #2237, allowing initializing config before app setup to give setup access to configuration settings. + * Fixed #2357, script_path was not properly carried through to dispatch. + * Fixed #2167, tgext.admin was not allowing for integrity errors to pass thru from post/put. + * Fixed #2373, Returning a list for an exposed json template now raises a meaningful exception. + * Fixed #2247, strict_c is now the default for the turbogears configuration in debug mode. + +== 2.1a1: (**October 2, 2009**) == + + * Refactor of Dispatch Code. + * TurboJson Requirement dropped. + * ToscaWidgets2 support added. + +== 2.0.1: (**June 21, 2009**) == + +=== Features === + + * ToscaWidgets made optional + * @expose(content_type="foo/var") now works #2280 thanks ondrejj (Docs are still outdated) + +=== Fixes === + + * SECURITY FIX: RestController and CrudRestController where not respecting allow_only. r6584 and r6587 (jorge.vargas,mramm) + * Fixed WSGI compatibility bug #2294 from Alex Morega at pycon sprints. + * Fixed Jinja2 renderer (#2310) thanks mbailey! + * Small test suite incompatibility on windows #2301 (chrisz) + * Fixed the very annoying login messages from i8n r6586 (everyone reported this :p) + +=== Backwards Incompatible Changes === + + * Reverted fix for #2241 the workaround should be at tgext.wsgiapps's HGController. If you add needed this fix you will need to fix it on your side. + +=== Known Issues === + + * None + +== 2.0rc1: (**March 3, 2009**) == + +=== Features === + + * None + +=== Fixes === + + * The authorization failure reason was being hardcoded when the Controller.allow_only attribute was used and the user was anonymous ([6520]) + * Fixes to the special __before__ and __after__ methods that are called before and after a controller's request is completed. + + +=== Backwards Incompatible Changes === + + * None. + +=== Known Issues === + + * None + +== 2.0b7: (**March 4, 2009**) == + +=== Features === + + * Now quickstarted applications with auth* enabled have a complete set of tests for protected areas and authentication, using repoze.who-testutil (#2198). + * Unitesting with SQLAlchemy enable is now possible + +=== Fixes === + + * .allow_only was broken in the !RootController (#2254) + * Controller Wide authorization now works as expected in all cases (even when you forget to call super's __init__) + * Now quickstarted applications are PEP-8 and PEP-257 compliant (#2223) + * Controller tests in the quickstart were not isolated, and did not have sample data from setup_app + * !TestModel in quickstarted applications was be moved to {app}.tests.models (#2249). + * repoze.what implements a workaround for a bug in Python < 2.6 whereby non-ASCII messages couldn't be logged (#2250) + * Controller tests are now isolated (#2244) + + +=== Backwards Incompatible Changes === + + * None. + +=== Known Issues === + + * being rejected from the Catwalk admin controllers caused you to loose your current login credentials. (#2262) + +== 2.0b6: (**Febuary 25, 2009**) == + +Beta 6 moves some dependencies into the Quickstarted project, so users who don't need them can edit them out. However, this means that you must {{{python setup.py develop}}} your quickstarted app. + +=== Features === + + * New default ModelTest class for unitesting #2200, quickstarted project uses it documentation pending but it's super easy to use :) + * Installing TurboGears now requires less dependencies thanks to #2176 + * TurboGears is now pip compatible but still not the default #2169 + * The repoze.what integration is now provided by the new [http://code.gustavonarea.net/repoze.what-pylons/ repoze.what-pylons] package. + * the webhelpers defined in lib/helpers are now only added to the template as helpers not as h. Previously Mako's h was not available because we were overriding it. This change was also sponsored by the campaign against one letter variables ;) + * For Python 2.6 users, introduced the class decorator @allow_only to set controller-wide authorization. It's an alternative to ''Controller.allow_only''. + * Now repoze.what predicates can be evaluated without passing the environ, as in: +{{{ +>>> from repoze.what.predicates import not_anonymous +>>> p = non_anonymous() +>>> bool(p) # Will return False if the user is anonymous; True otherwise +False +}}} + Also, now they're available as part of the TG-specific variables in the templates. (#2205). + +=== Fixes === + + * ''@tg.require'' set the status code to 401 when authorization was denied, regardless of whether the user was anonymous or not. From now on, a 403 status is used when the user was authenticated. + * repoze.what predicate messages in the quickstart are translated lazily (#2179; #2180). + * QUickstarted projects without Authorization were not starting due to a paster template error. + +=== Backwards Incompatible Changes === + + * Due to #2176 it is now required to run (python setup.py develop / pip install -e .) on a new quickstarted project to get the dependencies only used there. + * If you relied on the ''Controller._check_security()'' method, then you have to call it from ''Controller.!__before!__()'' because it's not called anymore by TG. + * if you used tg.testutils it's now gone r6323 (noone really used it) + + +=== Known Issues === + + * While installing tg.devtools, you'll get an error about "repoze.who" not being found. A workaround is to try to run the command, again. + * SQLAlchemy is not properly set up in test environments (#2243). + * The ''!__before!__()'' method of the controllers is not called. + +== 2.0b5: (**Febuary 4, 2009**) == + +=== Features === + + * Implemented post-login and post-logout pages in quickstarted applications thanks to the latest version of ''repoze.what-quickstart''. + * you can now manually set the content type in the controller and return a string, generator or webob response object without @expose messing with the response at all. + * by popular demand the tg.url function now supports getting a list of strings (like tg1) + +=== Fixes === + + * Authorization denial messages issued using TG's flash mechanism were using an invalid status and thus were not inside the typical color box. + * Two production config files were included in quickstart + * Pylons style __before__ was not supported. + * error pages were not styled properly + * repoze.who now respects SCRIPT_PATH so logout_handler will go to the right place in apps not mounted at the root of the url tree. + * SQLite install requirement added for python 2.4 users + * Several files were not included in the egg bundle for TG2 projects that were eggified. + * catwalk scrolling issue fixed + +=== Backwards Incompatible Changes === + + * None + +=== Known Issues === + +* None + +== 2.0b4: (January 24th, 2009) == + +Beta 4 is mostly a bugfix release, with no new features as we are approaching the 2.0 final release we need to squash out all the remaining known bugs and start working on release candidates. B4 is a recomended upgrade for all TG2 users who are using our authentication system because a threadsafety issue in repoze.what could cause authenentication denied messages to get sent to the wrong user if two users were denied access at "the same" time. + +Other key fixes, allow flash messages to cross authentication boundries, and updates to the admin so that it handles some relationships better, and to the mimetypes stuff so that .json works on all platforms. + +Beta 5 should be released next week with many more fixes. + +=== Features === + + * None + +=== Fixes === + + * Synchronized to repoze.what 1.0.1, which fixes an important bug that may affect production websites. + * Updated repoze.who so that it passes cookies along when doing a redirect for authorization -- this allows us to send flash messages across login requests. + * Updated sprox to fix an issue with some relationships + * Updated tgext.admin and catwalk to go with the new sprox release and fix some minor issues. + * Fixed problem where content type negotiation was not working on some platforms that don't include mimetypes for .json out of the box + * The traceback poster feature of the interactive debugger was not returning all the libraries in use, this is now fixed. + +=== Backwards Incompatible Changes === + + * None + +=== Known Issues === + + * Error page is not styled properly. + * catwalk opening page does not show a scrollbar -- even if one is needed + + More minor issues that are known in b4 and expected to be fixed in b5 can be found here: + +http://trac.turbogears.org/query?milestone=2.0b5 + +=== Contributors (in alphabetic order) === + + Gustavo Narea, Mark Ramm, Jorge Vargas, Christoper Perkins, Alberto Valverde. + +== 2.0b3: (January 20th, 2009) == + +=== Features === + + * HTTP Verb aware dispatch mechanisms for support of more REST based interfaces. + * you can now indicate a content type request using file extensions (index.html or index.json) + * Full unicode support in the url and redirect functions + * WSGIAppController and a new tgext.wsgiapps project to make mounting wsgi apps in TG2 even easier. + * Flash messages now use a plain cookie as a transport mechanism so they can be accessed and displayed with JS code which can make cacheing easier. + +=== Fixes === + + * Use a routes that supports unicode params, rather than doing our own escaping + * Some not authorized exceptions were not being caught by repoze.who because of the order in which the core middleware was added to the TG application (#2152). + +=== Backwards Incompatible Changes === + + * The "status_" prefix has been removed from the status codes of the flash messages so pre 2.0b3 quickstarted apps must be updated accordingly. TG now provides a helper to display them in the template, read this [http://groups.google.com/group/turbogears-trunk/msg/fcf0784c074f5dfc post] for more details. + +=== Known Issues === + + * none + +=== Contributors (in alphabetic order) === + + Gustavo Narea, Chris Perkins, Mark Ramm, Alberto Valverde, Jorge Vargas. + + +== 2.0b2: January 7, 2008) == + +=== Features === + * Debugger page now allows searching the turbogears mailing list + * Debugger page now posts TG dependencies when you post a traceback to the web + * app_cfg now allows you to regester call_on_startup and call_on_shutdown functions + * tg.url now wraps pylons.url to properly encode unicode strings to URL strings (as per the iri to url RFC) + * "error pages" now looked up and displayed by normal TurboGears controllers + +=== Fixes === + + * tg.url fixed so escaping of redirect params is no longer required. + +=== Contributors (in alphabetic order) === + +Mark Ramm + + + +== 2.0b1: (December 29th, 2008) == + +=== Features === + * It's now possible to set all the parameters used by the repoze.who !PluggableAuthenticationMiddleware through '''{yourapplication}.config.app_cfg'''. + +=== Fixes === + * Failing tests in tg2 trunk (#2059). + * Can't use @require decorator and require property in the same controller (#2063). + +=== Backwards Incompatible Changes === + + * tg.url no longer supports passing in a list of strings (these must be manually concatenated for now) + * tg.url no longer supports passing in a params dict (but you can use keyword arguments) + +=== Known Issues === + + * apps using authentication outside the root of the URL hierarchy will redirect to the absolute root rather than the app root. + +=== Contributors (in alphabetic order) === + +Florent Aide, Gustavo Narea, Chris Perkins, Mark Ramm, Jonathan Schemoul, Jorge Vargas + +=== Upgrading from 1.9.7b2 === + * Because of #2063, if you are using the controller-wide authorization functionality, you have to rename the "require" attribute to "alow_only". For example: +{{{ + class SomeSecureController(BaseController): + allow_only = predicates.has_permission('onePermission') + + @expose('my_package.template.index') + def index(self): + # do something here + + @expose('my_package.template.add') + @authorize.require(predicates.has_permission('specialPerm')) + def do_things(self, **kw): + # do other things here +}}} + +If you are using tg.cycle, tg.selector, tg.ipeek, or tg.checker in your templates, you'll have to add that functionality to your lib.helpers module and import it from there. There is some talk about asking for some of these things to be added to webhelpers, so if you use them you may want to chime in in support of that on the mailing list. + + +== 1.9.7b2 (December 2nd, 2008): == + +=== Features === + * [http://static.repoze.org/whatdocs/ repoze.what] replaces ''tgext.authorization''. TurboGears 1.9.7b2 requires [http://static.repoze.org/whatdocs/News.html#repoze-what-1-0b1-2008-11-26 repoze.what-1.0b1]. + * Authorization errors are now flashed. + * Any TG2 controller may take advantage of the ability to set controller-wide authorization using the '''require''' attribute. Therefore you are highly encouraged to remove the !SecureController class defined in '''{yourapplication}/lib/base.py'''. + +=== Fixes === + * Custom authentication settings defined in '''{yourapplication}.config.app_cfg''' were ignored (this is, custom repoze.who identifiers, authenticators, challengers and MD providers). + +=== Contributors (in alphabetic order) === +Gustavo Narea, Mark Ramm + +=== Upgrading from 1.9.7b1 === + * Replace all the ''tgext.authorization'' occurrences with ''repoze.what'' (it's safe to do a bulk find & replace). + * The '''@require''' decorator is now part of TG itself, not of ''repoze.what''. Now you should import it from the '''tg''' module. + * There are some [http://static.repoze.org/whatdocs/News.html#backwards-incompatible-changes backwards incompatible changes] from tgext.authorization-0.9a1 (used in the previous TG beta) and repoze.what-1.0b1. + +=== Known Issues === + * Toscawidgets does not render js_callbacks properly because of breakage from the simplejson. + +== 1.9.7b1 (October 29th, 2008): == + +=== Features === + * ''tgext.authorization'' replaces ''tg.ext.repoze.who''; the later becomes deprecated. ''tgext.authorization'' only deals with ''authorization'', and supports multiple sources to store your groups and permissions (not only databases), granting permissions to anonymous users, among other things. + +=== Fixes === + * Tests failed on quickstarted applications without identity (#1977). + * When running the ''websetup'' from the test suite, it used the development database instead of the in memory one (#1978). + * SimpleJson 2.0.4 now the minimum version. + +=== Contributors (in alphabetic order) === +Gustavo Narea, Mark Ramm + +=== Upgrading from 1.9.7a4 === + * If using DBTest, it's no longer mandatory to define the test database, as long as you define ''sqlalchemy.url'' in your ''test.ini'' file. So a DBTest subclass may look like this: +{{{ +class TestModel(DBTest): + """The base class for testing models in you TG project.""" + model = model +}}} + In fact, this is how that class looks like as of v1.9.7b1. + * If you were using '''tg.ext.repoze.who''', you should migrate to '''tgext.authorization''': + 1. Replace '''tg.ext.repoze.who''' by '''tgext.authorization''' in your controllers (tgext.authorization provides the same ''authorize'' module). + 1. In ''{yourpackage}.config.app_cfg'', the following settings are no longer necessary (and thus ignored): + * base_config.sa_auth.user_id_column + * base_config.sa_auth.password_encryption_method (it now relies on the ''verify_password'' method of your ''User'' model) + * base_config.sa_auth.users_table + * base_config.sa_auth.groups_table + * base_config.sa_auth.permissions_table + 1. Now, remove the following line: +{{{ +base_config.sa_auth = Bunch() +}}} + 1. Finally, if you're using a non-default value for '''base_config.sa_auth.user_criterion''', this following setting should be set in a different way: + + || '''Setting description''' || '''Before Beta 1''' || '''As of Beta 1''' || '''Sample definition as of Beta 1''' || + || Change the name of the column that contains the user name || base_config.sa_auth.user_criterion || base_config.sa_auth.translations.user_name || base_config.sa_auth.translations.user_name = 'person_name' || + + + +=== Known Issues === + * Toscawidgets does not render js_callbacks properly because of breakage from the simplejson. + + +== 1.9.7a4: == + +=== Features === + * New highly customizable replacement for Buffet renderers. + * These are not on by default, you must add base_config.use_legacy_renderer = False to your app_cfg.py file to use them. + * When using the new renderers, you have to switch genshi from dotted lookup to using paths+filenames. + * TG2 now supports automatic transactions, so you no longer have to explicitly commit transactions + * Transaction middleware supports cross-database transactions + * Transactions are not begun until the SQLAlchemy session becomes dirty, so no transaction overhead is wasted on requests that don't ever write to the database + * The SQLAlchemy metadata is no longer automatically bound in the config setup, so we can more easily support multiple database engines (eg., for master-slave replication). + * added start_response to the context, so we're easily able to use it to use WSGI applications anywhere + * added a use_wsgi_app() function that makes it very, very easy to mount a wsgi app in your tg2 controller, or use a TG2 controller as middleware. + * Improved support and documentation for returning a WebOb response object, so tg2 controllers can take over all aspects of defining the responce whenever that's needed. + * Simply set base_config.serve_static to False to stop your TG2 app from serving up static content, no manual editing of the middleware setup required. + * request, repsonse, etc all available from tg as well as pylons now + * default template namspace now has request, response, and tg variables automatically injected into it. + * quickstart now imports from tg wherever possible so there's less what's in tg what's in pylons + * a custom content type can now be set dynamically within a controller method by setting the content type in @expose to tg.controllers.CUSTOM_CONTENT_TYPE and using pylons.response.headers['Content-Type'] + * The declarative plugin of SQLAlchemy is now used in the default template, instead of the traditional method. + * TG1's DBTest has been ported to TG2. + + +=== Fixes === + * You were not able to use non-standard class names for User, Group, and Permissions in tg.ext.repoze.who + * Configuring an alternate location for controllers did not work with PylonsApp (now we have TGApp, an it will work)> + * the Content-Type header will not have `charset=utf8` appended to it when it is not required + +=== Contributors (in alphabetic order) === + +* Gustavo Narea, Mark Ramm, Matthew Sherborne, Alberto Valverde + +=== Upgrading from 1.9.7a3 === + + * The name of the `tg.config` module has been changed to `tg.configuration`. You must change your `config/app_cfg.py` file accordingly: +{{{ +-from tg.config import AppConfig, Bunch ++from tg.configuration import AppConfig, Bunch +}}} + + * You must define the user_class, group_class, and permission_class in app_config.py when using the authorizaiton plugin. +{{{ +-base_config.sa_auth.user = model.User ++base_config.sa_auth.user_class = model.User + base_config.sa_auth.user_criterion = model.User.user_name + base_config.sa_auth.user_id_column = 'user_id' ++base_config.sa_auth.group_class = model.Group ++base_config.sa_auth.permission_class = model.Permission +}}} + + * TG2 no longer binds the database engine to the DBSession or metadata so you can decide how you want to handle this (perhaps to re-bind the session dynamically to a different engine on a per-request basis, etc...). + +This is now done in the yourapp.model.init_model callback which is called when your app is loaded and passed a configured engine as a parameter. To upgrade an existing project just add one line to __init__ in you model directory, as shown here. + +{{{ +def init_model(engine): + DBSession.configure(bind=enigne) # <-- this, add this +}}} + + * `setup_tg_wsgi_app` has been removed from `tg.middleware`. It's now a method of `base_config`, so following lines must be removed from `config/middleware.py`: + +{{{ +-from pylons.wsgiapp import PylonsApp +-from tg.middleware import setup_tg_wsgi_app +}}} + + +Of course if you're starting a new project, all of this is done automatically for you by quickstart. + + * If you turn on new-style renderers, you must now provide the filename (and whatever path information is necessary) in expose, including the .html extension. Furthermore, we're now registering the template directory in the search path directly, so expose can be simpler. The old expose: + +{{{ + @expose('testproject.templates.index') + def index(self): + return dict(page='index') +}}} + +Should be replaced by the new expose: + +{{{ + @expose('index.html') + def index(self): + return dict(page='index') +}}} + +== 1.9.7a3 (July, 29, 2008): == + +=== Features === + + * TurboGears 2 now defaults to making multiple request parameters into a list that's passed to the controller (emulating the tg1 behavior). + * The base_config object now has a number of methods for those who need very fine grained control of how the middleware and environment are setup. + * tg2 now uses sphinx extensions to import code samples from svn, as well as to test the example code. + * new support of calling wsgi_apps from a TG2 controller method (see the use_wsgi_app function) + * added tg_vars to template namespace to more closely match tg1 + * Lots and lots of new docs covering: + * Updated TW docs + * Updated config docs + * Updated PyAMF integration docs + * Updated install and offline install docs + +=== Fixes === + + * Identity.py updated by splee to act more like tg1 + * Identity.py pep 8 compliance + * Fix for #1885 development.ini now runs only on localhost to avoid security issues related to the debugging interface being turned on. + * Added missing package requirement while using setup.py develop + * updated Paste dependencies, in order to work around an import appconfig issue mentioned on the mailing list + * updated the default quickstart project to look a bit nicer (thanks to Lukasz Szybalski) + +=== Contributors (in alphabetic order) === + +Florent Aide, Bruno J. M. Melo, Lee McFadden, Christopher Perkins, Mark Ramm, Sanjiv Singh, and Lukasz Szybalski. + +=== Upgrading from 1.9.7a2 === + + * The changes to the `base_config` object make it necessary to change `config/environment.py` and `config/middleware.py`. Without these changes your app will raise deprecation warnings. + + * in `config/environment.py`: + +{{{ +-load_environment = make_load_environment(base_config) ++load_environment = base_config.make_load_environment() +}}} + + * in `config/middleware.py`: + +{{{ ++-make_base_app = setup_tg_wsgi_app(load_environment, base_config) ++make_base_app = base_config.setup_tg_wsgi_app(load_environment) +}}} + + * You can also delete the from `tg.environment import make_load_environment` statements from both `config/app.py` and `config/environment.py` diff -Nru turbogears2-2.3.7/CONTRIBUTING.txt turbogears2-2.3.12/CONTRIBUTING.txt --- turbogears2-2.3.7/CONTRIBUTING.txt 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/CONTRIBUTING.txt 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,2 @@ +Have a look at http://turbogears.org/current-status.html +for how to contribute to the TurboGears project. diff -Nru turbogears2-2.3.7/.coveragerc turbogears2-2.3.12/.coveragerc --- turbogears2-2.3.7/.coveragerc 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/.coveragerc 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,5 @@ +[report] +omit = + tests/* + */python?.?/* + */site-packages/nose/* diff -Nru turbogears2-2.3.7/debian/changelog turbogears2-2.3.12/debian/changelog --- turbogears2-2.3.7/debian/changelog 2015-12-19 07:53:03.000000000 +0000 +++ turbogears2-2.3.12/debian/changelog 2019-01-26 22:18:03.000000000 +0000 @@ -1,3 +1,11 @@ +turbogears2 (2.3.12-1) unstable; urgency=medium + + * New upstream release. + * Update debhelper level to 11 . + * Update Standards-Version to 4.3.0 . + + -- Laszlo Boszormenyi (GCS) Sat, 26 Jan 2019 22:18:03 +0000 + turbogears2 (2.3.7-2) unstable; urgency=low * Drop unneeded turbojson dependency. diff -Nru turbogears2-2.3.7/debian/compat turbogears2-2.3.12/debian/compat --- turbogears2-2.3.7/debian/compat 2015-07-19 15:20:43.000000000 +0000 +++ turbogears2-2.3.12/debian/compat 2019-01-26 22:18:03.000000000 +0000 @@ -1 +1 @@ -9 +11 diff -Nru turbogears2-2.3.7/debian/control turbogears2-2.3.12/debian/control --- turbogears2-2.3.7/debian/control 2015-12-19 07:51:01.000000000 +0000 +++ turbogears2-2.3.12/debian/control 2019-01-26 22:18:03.000000000 +0000 @@ -2,14 +2,13 @@ Section: python Priority: optional Maintainer: Laszlo Boszormenyi (GCS) -Build-Depends: debhelper (>= 9), +Build-Depends: debhelper (>= 11), dh-python, python-all, python-pylons, python-setuptools -Standards-Version: 3.9.6 +Standards-Version: 4.3.0 Homepage: http://www.turbogears.org/ -X-Python-Version: >= 2.7 Package: python-turbogears2 Architecture: all diff -Nru turbogears2-2.3.7/debian/copyright turbogears2-2.3.12/debian/copyright --- turbogears2-2.3.7/debian/copyright 2015-07-19 15:20:57.000000000 +0000 +++ turbogears2-2.3.12/debian/copyright 2019-01-26 22:18:03.000000000 +0000 @@ -3,7 +3,7 @@ Packaging was taken over by Laszlo Boszormenyi (GCS) on Wed, 08 Feb 2012 12:35:23. -It was downloaded from http://www.turbogears.org/ +It was downloaded from https://github.com/TurboGears/tg2 Authors: Mark Ramm diff -Nru turbogears2-2.3.7/debian/patches/series turbogears2-2.3.12/debian/patches/series --- turbogears2-2.3.7/debian/patches/series 2015-07-13 08:36:57.000000000 +0000 +++ turbogears2-2.3.12/debian/patches/series 2019-01-26 22:18:03.000000000 +0000 @@ -1 +1 @@ -routes-mapper-minimize-by-default.patch +#routes-mapper-minimize-by-default.patch diff -Nru turbogears2-2.3.7/debian/rules turbogears2-2.3.12/debian/rules --- turbogears2-2.3.7/debian/rules 2015-07-13 13:01:52.000000000 +0000 +++ turbogears2-2.3.12/debian/rules 2019-01-26 22:18:03.000000000 +0000 @@ -1,4 +1,8 @@ #!/usr/bin/make -f +# -*- makefile -*- + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 PYTHONS:=$(shell pyversions -vr) PYTHON3S:=$(shell py3versions -vr) diff -Nru turbogears2-2.3.7/debian/watch turbogears2-2.3.12/debian/watch --- turbogears2-2.3.7/debian/watch 2015-07-19 15:33:49.000000000 +0000 +++ turbogears2-2.3.12/debian/watch 2019-01-26 22:18:03.000000000 +0000 @@ -1,3 +1,3 @@ -version=3 +version=4 https://github.com/TurboGears/tg2/releases \ .+/archive/tg(.+)\.(?:tar\.xz|txz|tar\.bz2|tbz2|tar\.gz|tgz) diff -Nru turbogears2-2.3.7/docs/find_docs.txt turbogears2-2.3.12/docs/find_docs.txt --- turbogears2-2.3.7/docs/find_docs.txt 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/docs/find_docs.txt 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,7 @@ +The source of the TurboGears 2.1 docs has been moved to: + +https://bitbucket.org/turbogears/tg-docs + +and the online docs can always be found at: + +http://turbogears.org/2.1/docs/ Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/FormsTut/formstutorial/public/images/grad_orange_11x100.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/FormsTut/formstutorial/public/images/grad_orange_11x100.png differ Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/FormsTut/formstutorial/public/images/header_inner.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/FormsTut/formstutorial/public/images/header_inner.png differ Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/FormsTut/formstutorial/public/images/strype.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/FormsTut/formstutorial/public/images/strype.png differ diff -Nru turbogears2-2.3.7/docs/project_code/FormsTut/tests/test_controller.py turbogears2-2.3.12/docs/project_code/FormsTut/tests/test_controller.py --- turbogears2-2.3.7/docs/project_code/FormsTut/tests/test_controller.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/docs/project_code/FormsTut/tests/test_controller.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from paste.fixture import TestApp + +import os.path +config = 'config:'+(os.path.abspath(os.path.basename(__name__)+'/../../development.ini#main')) + +app = TestApp(config) + +class TestTGController: + def test_index(self): + resp = app.get('/') + assert 'TurboGears 2 is a open source front-to-back web development' in resp.body, resp.body diff -Nru turbogears2-2.3.7/docs/project_code/Wiki-20/tests/test_controller.py turbogears2-2.3.12/docs/project_code/Wiki-20/tests/test_controller.py --- turbogears2-2.3.7/docs/project_code/Wiki-20/tests/test_controller.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/docs/project_code/Wiki-20/tests/test_controller.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from paste.fixture import TestApp + +import os.path +config = 'config:'+(os.path.abspath(os.path.basename(__name__)+'/../../development.ini#main')) + +app = TestApp(config) + +class TestTGController: + def test_index(self): + resp = app.get('/') + assert 'TurboGears 2 is a open source front-to-back web development' in resp.body, resp.body Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/Wiki-20/wiki20/public/images/grad_orange_11x100.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/Wiki-20/wiki20/public/images/grad_orange_11x100.png differ Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/Wiki-20/wiki20/public/images/header_inner.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/Wiki-20/wiki20/public/images/header_inner.png differ Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/docs/project_code/Wiki-20/wiki20/public/images/strype.png and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/docs/project_code/Wiki-20/wiki20/public/images/strype.png differ diff -Nru turbogears2-2.3.7/docs/testapp.mak turbogears2-2.3.12/docs/testapp.mak --- turbogears2-2.3.7/docs/testapp.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/docs/testapp.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,10 @@ + + + + Test Page + + + Hi ${ip} + + diff -Nru turbogears2-2.3.7/docs/testapp.py turbogears2-2.3.12/docs/testapp.py --- turbogears2-2.3.7/docs/testapp.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/docs/testapp.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,31 @@ +#!/usr/bin/python +from wsgiref.simple_server import make_server +import sys +import tg +from tg.configuration import AppConfig +from tg import TGController, expose + +class RootController(TGController): + @expose() + def index(self, *args, **kw): + return 'HELLO FROM %s' % tg.request.path + + @expose() + def somewhere(self): + return 'WELCOME SOMEWHERE' + + @expose('testapp.mak') + def test(self): + return dict(ip=tg.request.remote_addr) + +app_config = AppConfig(minimal=True) +app_config['tg.root_controller'] = RootController() + +#Setup support for MAKO. +app_config.renderers = ['mako'] +app_config.default_renderer = 'mako' +app_config.use_dotted_templatenames = False + +app = app_config.make_wsgi_app() +make_server('', 8080, app).serve_forever() + diff -Nru turbogears2-2.3.7/.gitignore turbogears2-2.3.12/.gitignore --- turbogears2-2.3.7/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/.gitignore 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,17 @@ +build/ +dist/ +*~ +*.egg +*.egg-info +*.pyc +*.pyo +*.swp +*.mak.py +*.DS_Store +.coverage +.idea +.noseids +.project +.pydevproject +.settings +nosetests.xml diff -Nru turbogears2-2.3.7/LICENSE.txt turbogears2-2.3.12/LICENSE.txt --- turbogears2-2.3.7/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/LICENSE.txt 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,10 @@ +This is the MIT license: +http://www.opensource.org/licenses/mit-license.php + +Copyright (c) 2005-2009 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. diff -Nru turbogears2-2.3.7/PKG-INFO turbogears2-2.3.12/PKG-INFO --- turbogears2-2.3.7/PKG-INFO 2015-10-13 12:58:27.000000000 +0000 +++ turbogears2-2.3.12/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 @@ -1,38 +0,0 @@ -Metadata-Version: 1.1 -Name: TurboGears2 -Version: 2.3.7 -Summary: Next generation TurboGears -Home-page: http://www.turbogears.org/ -Author: Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, Michael Pedersen, Alessandro Molina, and the TurboGears community -Author-email: mark.ramm@gmail.com, alberto@toscat.net, m.pedersen@icelus.org, amol@turbogears.org -License: MIT -Description: - TurboGears brings together a best of breed python tools - to create a flexible, full featured, and easy to use web - framework. - - TurboGears 2 provides an integrated and well tested set of tools for - everything you need to build dynamic, database driven applications. - It provides a full range of tools for front end javascript - develeopment, back database development and everything in between: - - * dynamic javascript powered widgets (ToscaWidgets2) - * automatic JSON generation from your controllers - * powerful, designer friendly XHTML based templating (Genshi) - * object or route based URL dispatching - * powerful Object Relational Mappers (SQLAlchemy) - - The latest development version is available in the - `TurboGears Git repositories`_. - - .. _TurboGears Git repositories: - https://github.com/TurboGears - -Keywords: turbogears -Platform: UNKNOWN -Classifier: Intended Audience :: Developers -Classifier: Environment :: Web Environment -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI diff -Nru turbogears2-2.3.7/README.rst turbogears2-2.3.12/README.rst --- turbogears2-2.3.7/README.rst 2014-10-03 19:43:50.000000000 +0000 +++ turbogears2-2.3.12/README.rst 2018-04-06 11:31:55.000000000 +0000 @@ -1,5 +1,5 @@ TurboGears -============== +========== .. image:: https://travis-ci.org/TurboGears/tg2.png?branch=development :target: https://travis-ci.org/TurboGears/tg2 @@ -7,26 +7,31 @@ .. image:: https://coveralls.io/repos/TurboGears/tg2/badge.png?branch=development :target: https://coveralls.io/r/TurboGears/tg2?branch=development -.. image:: https://pypip.in/v/TurboGears2/badge.png +.. image:: https://img.shields.io/pypi/v/TurboGears2.svg :target: https://pypi.python.org/pypi/TurboGears2 -.. image:: https://pypip.in/d/TurboGears2/badge.png - :target: https://pypi.python.org/pypi/TurboGears2 +.. image:: https://img.shields.io/pypi/pyversions/TurboGears2.svg + :target: https://pypi.python.org/pypi/TurboGears2 + +.. image:: https://img.shields.io/pypi/l/TurboGears2.svg + :target: https://pypi.python.org/pypi/TurboGears2 +.. image:: https://img.shields.io/twitter/follow/turbogearsorg.svg?style=social&label=Follow + :target: https://twitter.com/turbogearsorg TurboGears is a hybrid web framework able to act both as a Full Stack framework or as a Microframework. TurboGears helps you get going fast and gets out of your way when you want it! Support and Documentation ----------------------------- +------------------------- See the `TurboGears website `_ to get a quick overview of the framework or visit the `TurboGears Documentation `_ License ------------ +------- TurboGears is licensed under an MIT-style license (see LICENSE.txt). Other incorporated projects may be licensed under different licenses. diff -Nru turbogears2-2.3.7/setup.cfg turbogears2-2.3.12/setup.cfg --- turbogears2-2.3.7/setup.cfg 2015-10-13 12:58:27.000000000 +0000 +++ turbogears2-2.3.12/setup.cfg 2018-04-06 11:31:55.000000000 +0000 @@ -1,16 +1,11 @@ [aliases] +# A handy alias to make a release to pypi release = egg_info -RDb "" sdist bdist_egg register upload [nosetests] -exclude = who_testutil +exclude=who_testutil verbosity = 2 detailed-errors = 1 with-coverage = true cover-erase = true cover-package = tg - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - diff -Nru turbogears2-2.3.7/setup.py turbogears2-2.3.12/setup.py --- turbogears2-2.3.7/setup.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/setup.py 2018-04-06 11:31:55.000000000 +0000 @@ -20,15 +20,12 @@ 'Genshi >= 0.5.1', 'Mako', 'WebTest < 2.0', - 'routes', 'backlash >= 0.0.7', - 'sqlalchemy', 'raven < 4.1.0', 'formencode>=1.3.0a1', 'tw2.forms', 'Beaker', - 'Kajiki >= 0.4.4', - 'routes'] + 'Kajiki >= 0.4.4'] if py_version == (3, 2): # jinja2 2.7 is incompatible with Python 3.2 @@ -40,8 +37,11 @@ test_requirements.append('coverage') -if py_version != (3, 2): - # Ming is not compatible with Python3.2 +if py_version == (2, 6): + test_requirements.append('sqlalchemy < 1.2') + test_requirements.append('ming < 0.5.6') +else: + test_requirements.append('sqlalchemy') test_requirements.append('ming > 0.5.0') @@ -51,8 +51,8 @@ 'tw.forms']) install_requires=[ - 'WebOb >= 1.2', - 'crank >= 0.7.3, < 0.8', + 'WebOb >= 1.2, < 1.8.0', + 'crank >= 0.8.0, < 0.9.0', 'repoze.lru' ] @@ -70,8 +70,15 @@ classifiers=[ 'Intended Audience :: Developers', 'Environment :: Web Environment', - 'Programming Language :: Python :: 3', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: WSGI', ], diff -Nru turbogears2-2.3.7/tests/base.py turbogears2-2.3.12/tests/base.py --- turbogears2-2.3.7/tests/base.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/base.py 2018-04-06 11:31:55.000000000 +0000 @@ -7,6 +7,7 @@ from tg.appwrappers.i18n import I18NApplicationWrapper from tg.appwrappers.identity import IdentityApplicationWrapper from tg.appwrappers.session import SessionApplicationWrapper +from tg.configuration.utils import DependenciesList try: from xmlrpclib import loads, dumps @@ -25,7 +26,7 @@ from tg.wsgiapp import TemplateContext, TGApp, RequestLocals from tg.controllers import TGController -from .test_stack.baseutils import ControllerWrap, FakeRoutes, default_config +from .test_stack.baseutils import ControllerWrap, default_config data_dir = os.path.dirname(os.path.abspath(__file__)) session_dir = os.path.join(data_dir, 'session') @@ -47,17 +48,17 @@ tg.config['rendering_engines_options'] = default_config['rendering_engines_options'] config = default_config.copy() - config['application_wrappers'] = [ + config['application_wrappers'] = DependenciesList( I18NApplicationWrapper, IdentityApplicationWrapper, CacheApplicationWrapper, SessionApplicationWrapper - ] + ) if with_errors: config['errorpage.enabled'] = True config['errorpage.status_codes'] = [403, 404] - config['application_wrappers'].append(ErrorPageApplicationWrapper) + config['application_wrappers'].add(ErrorPageApplicationWrapper) config['session.enabled'] = True config['session.data_dir'] = session_dir @@ -70,7 +71,6 @@ app = TGApp(config=config) app.controller_classes['root'] = ControllerWrap(controller_klass) - app = FakeRoutes(app) app = RegistryManager(app) return TestApp(app) @@ -103,8 +103,6 @@ def get_response(self, **kargs): url = kargs.pop('_url', '/') - self.environ['tg.routes_dict'].update(kargs) - return self.app.get(url, extra_environ=self.environ) def post_response(self, **kargs): diff -Nru turbogears2-2.3.7/tests/empty_file.unknown turbogears2-2.3.12/tests/empty_file.unknown --- turbogears2-2.3.7/tests/empty_file.unknown 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/empty_file.unknown 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1 @@ +EMPTY Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/tests/fixtures/fakepackage.zip and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/tests/fixtures/fakepackage.zip differ diff -Nru turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/__init__.py turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/__init__.py --- turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/__init__.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff -Nru turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/app_globals.py turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/app_globals.py --- turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/app_globals.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/app_globals.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,4 @@ +class Globals(object): + def __init__(self): + super(Globals, self).__init__() + self.text = 'HI!!' diff -Nru turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/helpers.py turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/helpers.py --- turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/helpers.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/helpers.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,3 @@ + +def get_text(): + return 'HI!!' \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/__init__.py turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/__init__.py --- turbogears2-2.3.7/tests/fixtures/package_with_helpers_submodule/lib/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/fixtures/package_with_helpers_submodule/lib/__init__.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/tests/i18n/de/LC_MESSAGES/tests.mo and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/tests/i18n/de/LC_MESSAGES/tests.mo differ diff -Nru turbogears2-2.3.7/tests/i18n/de/LC_MESSAGES/tests.po turbogears2-2.3.12/tests/i18n/de/LC_MESSAGES/tests.po --- turbogears2-2.3.7/tests/i18n/de/LC_MESSAGES/tests.po 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/i18n/de/LC_MESSAGES/tests.po 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,22 @@ +# German translations for tests. +# +msgid "" +msgstr "" +"Project-Id-Version: tests 0.0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2009-07-07 07:07+0700\n" +"PO-Revision-Date: 2009-07-07 07:17+0700\n" +"Last-Translator: FULL NAME \n" +"Language-Team: de \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" + +#: tests/controllers/root.py:13 +msgid "Your application is now running" +msgstr "Ihre Anwendung läuft jetzt einwandfrei" + +msgid "This is a fallback" +msgstr "Dies ist ein zurückfallen" Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/tests/i18n/kr/LC_MESSAGES/tests.mo and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/tests/i18n/kr/LC_MESSAGES/tests.mo differ diff -Nru turbogears2-2.3.7/tests/i18n/kr/LC_MESSAGES/tests.po turbogears2-2.3.12/tests/i18n/kr/LC_MESSAGES/tests.po --- turbogears2-2.3.7/tests/i18n/kr/LC_MESSAGES/tests.po 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/i18n/kr/LC_MESSAGES/tests.po 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,20 @@ +# Russian translations for tests. +# +msgid "" +msgstr "" +"Project-Id-Version: tests 0.0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2009-07-07 07:07+0700\n" +"PO-Revision-Date: 2009-07-07 07:17+0700\n" +"Last-Translator: FULL NAME \n" +"Language-Team: ru \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" + +#: tests/controllers/root.py:13 +msgid "Your application is now running" +msgstr "Ваши приложение успешно запущено" Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/tests/i18n/ru/LC_MESSAGES/tests.mo and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/tests/i18n/ru/LC_MESSAGES/tests.mo differ diff -Nru turbogears2-2.3.7/tests/i18n/ru/LC_MESSAGES/tests.po turbogears2-2.3.12/tests/i18n/ru/LC_MESSAGES/tests.po --- turbogears2-2.3.7/tests/i18n/ru/LC_MESSAGES/tests.po 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/i18n/ru/LC_MESSAGES/tests.po 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,20 @@ +# Russian translations for tests. +# +msgid "" +msgstr "" +"Project-Id-Version: tests 0.0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2009-07-07 07:07+0700\n" +"PO-Revision-Date: 2009-07-07 07:17+0700\n" +"Last-Translator: FULL NAME \n" +"Language-Team: ru \n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" + +#: tests/controllers/root.py:13 +msgid "Your application is now running" +msgstr "Ваши приложение успешно запущено" diff -Nru turbogears2-2.3.7/tests/non_overridden.html turbogears2-2.3.12/tests/non_overridden.html --- turbogears2-2.3.7/tests/non_overridden.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/non_overridden.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,8 @@ + + + + + +Not overridden. + + diff -Nru turbogears2-2.3.7/tests/overridden.html turbogears2-2.3.12/tests/overridden.html --- turbogears2-2.3.7/tests/overridden.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/overridden.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,8 @@ + + + overridden.html + + +This is overridden. + + diff -Nru turbogears2-2.3.7/tests/overridden_js.mak turbogears2-2.3.12/tests/overridden_js.mak --- turbogears2-2.3.7/tests/overridden_js.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/overridden_js.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1 @@ +var state='This is overridden.'; diff -Nru turbogears2-2.3.7/tests/test_configuration_dictionary.py turbogears2-2.3.12/tests/test_configuration_dictionary.py --- turbogears2-2.3.7/tests/test_configuration_dictionary.py 2015-06-11 08:06:39.000000000 +0000 +++ turbogears2-2.3.12/tests/test_configuration_dictionary.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,95 +0,0 @@ -from nose.tools import raises -#from tg.configuration.utils import ConfigurationBunch - - -class TestConfigurationDictionary(object): - def setup(self): - self.d = ConfigurationBunch() - -""" - def test_plain_to_nested(self): - d = self.d - d['1st.2nd.3rd'] = 'HELLO' - assert d['1st']['2nd']['3rd'] == 'HELLO' - - def test_nested_to_plain(self): - d = self.d - d['1st'] = {} - d['1st']['2nd'] = {} - d['1st']['2nd']['3rd'] = 'HELLO' - assert d['1st.2nd.3rd'] == 'HELLO' - - def test_plain_to_plain(self): - d = self.d - d['1st.2nd.3rd'] = 'HELLO' - assert d['1st.2nd.3rd'] == 'HELLO' - - def test_nested_to_attrs(self): - d = self.d - d['st1'] = {} - d['st1']['nd2'] = {} - d['st1']['nd2']['rd3'] = 'HELLO' - assert d.st1.nd2.rd3 == 'HELLO' - - def test_plain_to_attrs(self): - d = self.d - d['st1.nd2.rd3'] = 'HELLO' - assert d.st1.nd2.rd3 == 'HELLO' - - def test_iterate(self): - d = self.d - d['simple.sub'] = {'sub1': [3, 4], 'sub2': 'hi', 'sub3': {'subsub1': 7, 'subsub2': 8}} - d['simple.sub.x'] = 'hi' - - flatkeys = list(iter(d)) - assert flatkeys == ['simple.sub.x', 'simple.sub.sub2', 'simple.sub.sub3.subsub2', - 'simple.sub.sub3.subsub1', 'simple.sub.sub1'], flatkeys - - @raises(KeyError) - def test_delete_plain(self): - d = self.d - d['st1'] = {} - d['st1']['nd2'] = {} - d['st1']['nd2']['rd3'] = 'HELLO' - d.pop('st1.nd2.rd3', None) - - d['st1']['nd2']['rd3'] - - @raises(AttributeError) - def test_plain_to_attrs_not_found(self): - d = self.d - d['st1.nd2.rd3'] = 'HELLO' - d.st1.nd2.rd4 - - def test_set_plain(self): - self.d['hi'] = 5 - self.d['simple.sub.sub1'] = [3, 4] - self.d['simple.sub.sub2'] = [3, 4] - assert sorted(list(self.d.keys())) == sorted(['hi', 'simple']), self.d - assert 'sub' in self.d['simple'], self.d - assert len(self.d['simple.sub'].keys()) == 2 - - def test_set_subdict(self): - d = self.d - d['simple.sub'] = {'sub1': [3, 4], 'sub2': 'hi', 'sub3': {'subsub1': 7, 'subsub2': 8}} - d['simple.sub.x'] = 'hi' - assert len(d) == 1, d - assert d['simple'] == {'sub': {'x': 'hi', 'sub1': [3, 4], 'sub2': 'hi', 'sub3': {'subsub1': 7, 'subsub2': 8}}} - - def test_get_subdict(self): - self.test_set_subdict() - assert self.d['simple.sub.sub3'] == {'subsub1': 7, 'subsub2': 8} - - @raises(TypeError) - def test_odd_parents_and_children1(self): - self.d['parent'] = 5 - self.d['parent.child'] = 3 - - @raises(TypeError) - def test_odd_parents_and_children2(self): - self.d['parent.child'] = 3 - self.d['parent'] = 3 - - # parent is now an int so it should fail - self.d['parent.child'] -""" \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_configuration.py turbogears2-2.3.12/tests/test_configuration.py --- turbogears2-2.3.7/tests/test_configuration.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_configuration.py 2018-04-06 11:31:55.000000000 +0000 @@ -7,6 +7,7 @@ from datetime import datetime from sqlalchemy.orm import scoped_session from sqlalchemy.orm import sessionmaker +from sqlalchemy.engine import Engine from ming import Session from ming.orm import ThreadLocalORMSession from tg.configuration.hooks import _TGGlobalHooksNamespace @@ -40,6 +41,7 @@ milestones._reset_all() teardown_session_dir() + class PackageWithModel: __name__ = 'tests' __file__ = __file__ @@ -53,9 +55,13 @@ def remove(self): self.DBSESSION_REMOVED=True - @classmethod - def init_model(package, engine): - pass + def init_model(self, engine): + if isinstance(engine, Engine): + # SQLA + return self.DBSession + else: + # Ming + return dict(ming=True) class lib: class app_globals: @@ -63,6 +69,7 @@ pass PackageWithModel.__name__ = 'tests' + class UncopiableList(list): """ This is to test configuration methods that make a copy @@ -79,6 +86,7 @@ def begin(self): self.aborted = False self.doomed = False + return self def abort(self): self.aborted = True @@ -189,6 +197,13 @@ milestones._reset_all() tg.hooks = _TGGlobalHooksNamespace() # Reset hooks + def test_reqlocal_configuration_dictionary(self): + self.config['RANDOM_VALUE'] = 5 + conf = self.config._init_config({}, {}) + + assert config['RANDOM_VALUE'] == 5 + assert len(config) == len(conf) + def test_get_root(self): current_root_module = self.config['paths']['root'] assert self.config._get_root_module() == 'tests.controllers.root', self.config._get_root_module() @@ -288,22 +303,19 @@ conf['ming.url'] = 'mim://' app = conf.make_wsgi_app() - def test_enable_routes(self): - conf = AppConfig(minimal=True) - conf.enable_routes = True - app = conf.make_wsgi_app() - - a = TGApp() - assert a.enable_routes == True - - config.pop('routes.map') - config.pop('enable_routes') - def test_create(self): pass def test_setup_helpers_and_globals(self): - self.config.setup_helpers_and_globals() + self.config._setup_helpers_and_globals(self.config._init_config({}, {})) + + def test_setup_helpers_and_globals_custom_backward_compatible(self): + def custom_helpers(conf): + conf['helpers'] = 'YES!' + + object.__setattr__(self.config, '_setup_helpers_and_globals', custom_helpers) + conf = self.config.make_load_environment()({}, {}) + assert conf['helpers'] == 'YES!', conf.get('helpers') def test_setup_sa_auth_backend(self): class ConfigWithSetupAuthBackend(self.config.__class__): @@ -313,7 +325,19 @@ self.called.append(True) conf = ConfigWithSetupAuthBackend() - conf.setup_auth() + conf._setup_auth(conf._init_config({}, {})) + + assert len(ConfigWithSetupAuthBackend.called) >= 1 + + def test_setup_sa_auth_custom_backward_compatible(self): + class ConfigWithSetupAuthBackend(self.config.__class__): + called = [] + + def _setup_auth(self, app_config): + self.called.append(True) + + conf = ConfigWithSetupAuthBackend() + conf._setup_auth(conf._init_config({}, {})) assert len(ConfigWithSetupAuthBackend.called) >= 1 @@ -347,7 +371,7 @@ def test_custom_transaction_manager(self): class CustomAppConfig(AppConfig): - def add_tm_middleware(self, app): + def _add_tm_middleware(self, config, app): self.did_perform_custom_tm = True return app @@ -372,7 +396,7 @@ # The transaction manager wrapper should not have been enabled. tgapp = TGApp() wrapper = tgapp.wrapped_dispatch - while wrapper != tgapp.dispatch: + while wrapper != tgapp._dispatch: assert not isinstance(wrapper, TransactionApplicationWrapper) wrapper = wrapper.next_handler @@ -639,56 +663,47 @@ app = conf.make_wsgi_app() assert conf.use_transaction_manager is False + def test_setup_persistence_custom_backward_compatible(self): + self.config.package = PackageWithModel() + + def custom_persistence(conf): + conf['gotcha'] = 'YES!' + + object.__setattr__(self.config, '_setup_persistence', custom_persistence) + conf = self.config.make_load_environment()({}, {}) + assert conf['gotcha'] == 'YES!', conf + def test_setup_sqla_persistance(self): - config['sqlalchemy.url'] = 'sqlite://' + self.config['sqlalchemy.url'] = 'sqlite://' self.config.use_sqlalchemy = True self.config.package = PackageWithModel() - self.config.setup_persistence() - - self.config.use_sqlalchemy = False + self.config._setup_persistence(self.config._init_config({}, {})) def test_setup_sqla_balanced(self): - config['sqlalchemy.master.url'] = 'sqlite://' - config['sqlalchemy.slaves.slave1.url'] = 'sqlite://' + self.config['sqlalchemy.master.url'] = 'sqlite://' + self.config['sqlalchemy.slaves.slave1.url'] = 'sqlite://' self.config.use_sqlalchemy = True self.config.package = PackageWithModel() - self.config.setup_persistence() - - self.config.use_sqlalchemy = False - config.pop('sqlalchemy.master.url') - config.pop('sqlalchemy.slaves.slave1.url') + self.config._setup_persistence(self.config._init_config({}, {})) @raises(TGConfigError) def test_setup_sqla_balanced_prevent_slave_named_master(self): - config['sqlalchemy.master.url'] = 'sqlite://' - config['sqlalchemy.slaves.master.url'] = 'sqlite://' + self.config['sqlalchemy.master.url'] = 'sqlite://' + self.config['sqlalchemy.slaves.master.url'] = 'sqlite://' self.config.use_sqlalchemy = True self.config.package = PackageWithModel() - try: - self.config.setup_persistence() - except: - raise - finally: - self.config.use_sqlalchemy = False - config.pop('sqlalchemy.master.url') - config.pop('sqlalchemy.slaves.master.url') + self.config._setup_persistence(self.config._init_config({}, {})) @raises(TGConfigError) def test_setup_sqla_balanced_no_slaves(self): - config['sqlalchemy.master.url'] = 'sqlite://' + self.config['sqlalchemy.master.url'] = 'sqlite://' self.config.use_sqlalchemy = True self.config.package = PackageWithModel() - try: - self.config.setup_persistence() - except: - raise - finally: - self.config.use_sqlalchemy = False - config.pop('sqlalchemy.master.url') + self.config._setup_persistence(self.config._init_config({}, {})) def test_setup_ming_persistance(self): class RootController(TGController): @@ -711,7 +726,7 @@ tgapp = tgapp.app ming_handler = tgapp.wrapped_dispatch - while ming_handler != tgapp.dispatch: + while ming_handler != tgapp._dispatch: if isinstance(ming_handler, MingApplicationWrapper): break ming_handler = ming_handler.next_handler @@ -755,7 +770,7 @@ tgapp = tgapp.app ming_handler = tgapp.wrapped_dispatch - while ming_handler != tgapp.dispatch: + while ming_handler != tgapp._dispatch: if isinstance(ming_handler, MingApplicationWrapper): break ming_handler = ming_handler.next_handler @@ -797,6 +812,27 @@ # Looks like ming has empty dstore.name when using MIM. assert dstore_name == '', dstore + def test_setup_sqla_and_ming_both(self): + package = PackageWithModel() + base_config = AppConfig(minimal=True, root_controller=None) + base_config.package = package + base_config.model = package.model + base_config.use_ming = True + base_config['ming.url'] = 'mim://inmemdb' + base_config.use_sqlalchemy = True + base_config['sqlalchemy.url'] = 'sqlite://' + + app = base_config.make_wsgi_app() + assert app is not None + + assert config['MingSession'], config + assert config['tg.app_globals'].ming_datastore, config['tg.app_globals'] + + assert config['SQLASession'], config + assert config['tg.app_globals'].sa_engine, config['tg.app_globals'] + + assert config['DBSession'] is config['SQLASession'], config + def test_setup_ming_persistance_with_url_and_db(self): package = PackageWithModel() conf = AppConfig(minimal=True, root_controller=None) @@ -878,11 +914,12 @@ self.config.sa_auth.cookie_secret = 'dummy' self.config.sa_auth.password_encryption_method = 'sha' - self.config.setup_auth() - self.config.add_auth_middleware(None, None) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) def test_add_static_file_middleware(self): - self.config.add_static_file_middleware(None) + self.config._add_static_file_middleware(self.config._init_config({}, {}), None) def test_setup_sqla_auth(self): if PY3: raise SkipTest() @@ -914,7 +951,7 @@ def test_setup_ming_auth(self): self.config.auth_backend = 'ming' - self.config.setup_auth() + self.config._setup_auth(self.config._init_config({}, {})) assert 'sa_auth' in config self.config.auth_backend = None @@ -947,8 +984,8 @@ @raises(TGConfigError) def test_missing_secret(self): self.config.auth_backend = 'sqlalchemy' - config.pop('session.secret', None) - self.config.setup_auth() + self.config.pop('session.secret', None) + self.config._setup_auth(self.config._init_config({}, {})) def test_sessions_enabled(self): class RootController(TGController): @@ -1041,7 +1078,7 @@ def test_controler_wrapper_setup(self): orig_caller = self.config.controller_caller self.config.controller_wrappers = [] - self.config._setup_controller_wrappers() + self.config._setup_controller_wrappers(self.config._init_config({}, {})) assert config['controller_caller'] == orig_caller def controller_wrapper(caller): @@ -1051,13 +1088,13 @@ orig_caller = self.config.controller_caller self.config.controller_wrappers = [controller_wrapper] - self.config._setup_controller_wrappers() + self.config._setup_controller_wrappers(self.config._init_config({}, {})) assert config['controller_caller'].__name__ == controller_wrapper(orig_caller).__name__ def test_backward_compatible_controler_wrapper_setup(self): orig_caller = self.config.controller_caller self.config.controller_wrappers = [] - self.config._setup_controller_wrappers() + self.config._setup_controller_wrappers(self.config._init_config({}, {})) assert config['controller_caller'] == orig_caller def controller_wrapper(app_config, caller): @@ -1067,7 +1104,7 @@ orig_caller = self.config.controller_caller self.config.controller_wrappers = [controller_wrapper] - self.config._setup_controller_wrappers() + self.config._setup_controller_wrappers(self.config._init_config({}, {})) deprecated_wrapper = config['controller_caller'].wrapper assert deprecated_wrapper.__name__ == controller_wrapper(self.config, orig_caller).__name__ @@ -1292,11 +1329,12 @@ conf.register_wrapper(AppWrapper5, after=AppWrapper3) milestones.environment_loaded.reach() - assert conf.application_wrappers[0] == AppWrapper1 - assert conf.application_wrappers[1] == AppWrapper2 - assert conf.application_wrappers[2] == AppWrapper3 - assert conf.application_wrappers[3] == AppWrapper4 - assert conf.application_wrappers[4] == AppWrapper5 + app_wrappers = list(conf.application_wrappers.values()) + assert app_wrappers[0] == AppWrapper1 + assert app_wrappers[1] == AppWrapper2 + assert app_wrappers[2] == AppWrapper3 + assert app_wrappers[3] == AppWrapper4 + assert app_wrappers[4] == AppWrapper5 def test_wrap_app(self): class RootController(TGController): @@ -1324,7 +1362,7 @@ renderers = self.config.renderers self.config.renderers = ['unknwon'] try: - self.config._setup_renderers() + self.config._setup_renderers(self.config._init_config({}, {})) except TGConfigError: self.config.renderers = renderers else: @@ -1333,22 +1371,25 @@ @raises(TGConfigError) def test_cookie_secret_required(self): self.config.sa_auth = {} - self.config.setup_auth() - self.config.add_auth_middleware(None, False) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) def test_sqla_auth_middleware(self): if PY3: raise SkipTest() self.config.auth_backend = 'sqlalchemy' + self.config.skip_authentication = True self.config['sa_auth'] = {'authmetadata': ApplicationAuthMetadata(), 'dbsession': None, 'user_class':None, 'cookie_secret':'12345', 'authenticators':UncopiableList([('default', None)])} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) - authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] + authenticators = [x[0] for x in cfg['sa_auth']['authenticators']] assert 'cookie' in authenticators assert 'sqlauth' in authenticators @@ -1365,8 +1406,9 @@ 'translations': {'user_name':'SomethingElse'}, 'cookie_secret':'12345', 'authenticators':UncopiableList([('default', None)])} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert 'cookie' in authenticators @@ -1395,8 +1437,9 @@ 'authenticators':UncopiableList([('superfirst', None), ('default', None)])} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert authenticators[1] == 'superfirst' @@ -1417,8 +1460,9 @@ #In this case we can just test it doesn't crash #as the sa_auth dict doesn't have an authenticators key to check for - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) self.config['sa_auth'] = {} self.config.auth_backend = None @@ -1512,8 +1556,9 @@ 'user_class':None, 'cookie_secret':'12345', 'authenticators':UncopiableList([('default', None)])} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert 'cookie' in authenticators @@ -1531,8 +1576,9 @@ self.config.auth_backend = None self.config['sa_auth'] = {'authmetadata': ApplicationAuthMetadata(), 'cookie_secret':'12345'} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert 'cookie' in authenticators @@ -1549,8 +1595,9 @@ 'user_class':None, 'cookie_secret':'12345', 'authenticators':UncopiableList([('default', None)])} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert 'cookie' in authenticators @@ -1559,6 +1606,40 @@ self.config['sa_auth'] = {} self.config.auth_backend = None + def test_auth_setup_default_identifier(self): + self.config.auth_backend = 'sqlalchemy' + self.config['sa_auth'] = {'authmetadata': ApplicationAuthMetadataWithAuthentication(), + 'dbsession': None, + 'user_class':None, + 'cookie_secret':'12345', + 'identifiers': UncopiableList([('default', None)])} + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) + + identifiers = [x[0] for x in self.config['sa_auth']['identifiers']] + assert 'cookie' in identifiers + + self.config['sa_auth'] = {} + self.config.auth_backend = None + + def test_auth_setup_custom_identifier(self): + self.config.auth_backend = 'sqlalchemy' + self.config['sa_auth'] = {'authmetadata': ApplicationAuthMetadataWithAuthentication(), + 'dbsession': None, + 'user_class':None, + 'cookie_secret':'12345', + 'identifiers': UncopiableList([('custom', None)])} + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) + + identifiers = [x[0] for x in self.config['sa_auth']['identifiers']] + assert 'custom' in identifiers + + self.config['sa_auth'] = {} + self.config.auth_backend = None + def test_auth_middleware_doesnt_touch_authenticators(self): # Checks that the auth middleware process doesn't touch original authenticators # list, to prevent regressions on this. @@ -1568,8 +1649,9 @@ 'user_class':None, 'cookie_secret':'12345', 'authenticators':[('default', None)]} - self.config.setup_auth() - self.config.add_auth_middleware(None, True) + cfg = self.config._init_config({}, {}) + self.config._setup_auth(cfg) + self.config._add_auth_middleware(cfg, None) authenticators = [x[0] for x in self.config['sa_auth']['authenticators']] assert len(authenticators) == 1 @@ -1599,7 +1681,7 @@ tw.api.make_middleware = fake_make_middleware config['toscawidgets.framework.resource_variant'] = 'min' - self.config.add_tosca_middleware(None) + self.config._add_tosca_middleware(config, None) config.pop('toscawidgets.framework.resource_variant', None) def test_error_middleware_disabled_with_optimize(self): @@ -1696,11 +1778,11 @@ pass a = TGApp() - testmode, __, __ = a.setup_app_env({'paste.registry':FakeRegistry()}) + testmode, __, __ = a._setup_app_env({'paste.registry':FakeRegistry()}) assert testmode is False - testmode, __, __ = a.setup_app_env({'paste.registry':FakeRegistry(), - 'paste.testing_variables':{}}) + testmode, __, __ = a._setup_app_env({'paste.registry':FakeRegistry(), + 'paste.testing_variables':{}}) assert testmode is True def test_application_no_controller_hijacking(self): @@ -1778,6 +1860,28 @@ resp = app.get('/test', status=403) assert 'ERROR!!!' in resp, resp + def test_error_document_passthrough(self): + class ErrorController(TGController): + @expose() + def document(self, *args, **kw): + return 'ERROR!!!' + + class RootController(TGController): + error = ErrorController() + @expose() + def test(self): + request.disable_error_pages() + abort(403, detail='Custom Detail') + + conf = AppConfig(minimal=True, root_controller=RootController()) + conf['errorpage.enabled'] = True + conf['errorpage.handle_exceptions'] = False + app = conf.make_wsgi_app(full_stack=True) + app = TestApp(app) + + resp = app.get('/test', status=403) + assert 'Custom Detail' in resp, resp + def test_custom_old_error_document(self): class ErrorController(TGController): @expose() @@ -2226,6 +2330,20 @@ app = TestApp(app) assert 'HI!' in app.get('/test') + def test_make_app_with_appglobals_submodule(self): + class RootController(TGController): + @expose('') + def test(self, *args, **kwargs): + return tg.app_globals.text + + conf = AppConfig(minimal=True, root_controller=RootController()) + + from .fixtures import package_with_helpers_submodule + conf['package'] = package_with_helpers_submodule + app = conf.make_wsgi_app() + app = TestApp(app) + assert 'HI!!' in app.get('/test') + def test_make_app_with_custom_helpers(self): class RootController(TGController): @expose('') @@ -2241,4 +2359,32 @@ conf.helpers = FakeHelpers() app = conf.make_wsgi_app() app = TestApp(app) - assert 'HI!' in app.get('/test') \ No newline at end of file + assert 'HI!' in app.get('/test') + + def test_make_app_with_helpers_submodule(self): + class RootController(TGController): + @expose('') + def test(self, *args, **kwargs): + return config['helpers'].get_text() + + conf = AppConfig(minimal=True, root_controller=RootController()) + + from .fixtures import package_with_helpers_submodule + conf['package'] = package_with_helpers_submodule + app = conf.make_wsgi_app() + app = TestApp(app) + assert 'HI!!' in app.get('/test') + + def test_make_app_without_load_environment(self): + class RootController(TGController): + @expose() + def test(self, *args, **kwargs): + return 'Helpers: %s' % tg.config.get('helpers') + + conf = AppConfig(minimal=True, root_controller=RootController()) + cfg = conf._init_config({}, {}) # Manually call init_config otherwise no tg.config is pushed. + app = conf.setup_tg_wsgi_app(load_environment=None)() + app = TestApp(app) + + resp = app.get('/test') + assert resp.text == 'Helpers: None', resp \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_configuration_util.py turbogears2-2.3.12/tests/test_configuration_util.py --- turbogears2-2.3.7/tests/test_configuration_util.py 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_configuration_util.py 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +from nose.tools import raises + +from tg.configuration.utils import DependenciesList + +class DLEntry1: pass +class DLEntry2: pass +class DLEntry3: pass +class DLEntry4: pass +class DLEntry5: pass + + +class TestDependenciesList(object): + def test_basic_with_classes(self): + dl = DependenciesList() + + dl.add(DLEntry2) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry3) + dl.add(DLEntry1, after=False) + dl.add(DLEntry5, after=DLEntry3) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_basic_iter(self): + dl = DependenciesList() + + dl.add(DLEntry2) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry3) + dl.add(DLEntry1, after=False) + dl.add(DLEntry5, after=DLEntry3) + + visisted = [] + for key, value in dl: + visisted.append(key) + + assert visisted == ['DLEntry1', 'DLEntry2', 'DLEntry3', 'DLEntry4', 'DLEntry5'] + + def test_basic_repr(self): + dl = DependenciesList() + + dl.add(DLEntry2) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry3) + dl.add(DLEntry1, after=False) + dl.add(DLEntry5, after=DLEntry3) + + expected = "" + assert repr(dl) == expected + + def test_basic_with_ids(self): + dl = DependenciesList() + + dl.add(DLEntry2, 'num2') + dl.add(DLEntry4, 'num4', after='num3') + dl.add(DLEntry3, 'num3') + dl.add(DLEntry1, 'num1', after=False) + dl.add(DLEntry5, 'num5', after='num3') + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_reversed_with_ids(self): + dl = DependenciesList() + + dl.add(DLEntry5, 'num5', after='num4') + dl.add(DLEntry4, 'num4', after='num3') + dl.add(DLEntry3, 'num3', after='num2') + dl.add(DLEntry2, 'num2', after='num1') + dl.add(DLEntry1, 'num1') + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_multiple_with_ids(self): + dl = DependenciesList() + dl.add(DLEntry1) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry5, after=DLEntry4) + dl.add(DLEntry2, after=DLEntry1) + dl.add(DLEntry3, after=DLEntry1) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_multiple_with_missing_step(self): + dl = DependenciesList() + dl.add(DLEntry1) + dl.add(DLEntry3, after=DLEntry2) + dl.add(DLEntry2, after=DLEntry5) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + + def test_objects_instead_of_classes(self): + dl = DependenciesList() + dl.add(DLEntry1(), 'DLEntry1') + dl.add(DLEntry3(), 'DLEntry3', after=DLEntry2) + dl.add(DLEntry2(), 'DLEntry2', after=DLEntry5) + + dl_values = list(dl.values()) + assert dl_values[0].__class__ == DLEntry1 + assert dl_values[1].__class__ == DLEntry2 + assert dl_values[2].__class__ == DLEntry3 + + @raises(ValueError) + def test_objects_must_have_key(self): + dl = DependenciesList() + dl.add(DLEntry1()) + + @raises(ValueError) + def test_after_cannot_be_an_instance(self): + dl = DependenciesList() + dl.add(DLEntry1(), key='DLEntry1') + dl.add(DLEntry2(), key='DLEntry2', after=DLEntry1()) + + def test_after_everything_else(self): + dl = DependenciesList() + + dl.add(DLEntry2, after=True) + dl.add(DLEntry5, after=True) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry3, after=DLEntry2) + + dl.add(DLEntry1) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry3 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_replacing_elements_with_key(self): + dl = DependenciesList() + + dl.add(DLEntry2, 'num2') + dl.add(DLEntry4, 'num4', after='num3') + dl.add(DLEntry3, 'num3') + dl.add(DLEntry1, 'num1', after=False) + dl.add(DLEntry5, 'num5', after='num3') + + dl.replace('num3', DLEntry1) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry1 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + def test_replacing_elements_with_classes(self): + dl = DependenciesList() + + dl.add(DLEntry2) + dl.add(DLEntry4, after=DLEntry3) + dl.add(DLEntry3) + dl.add(DLEntry1, after=False) + dl.add(DLEntry5, after=DLEntry3) + + dl.replace(DLEntry3, DLEntry1) + + dl_values = list(dl.values()) + assert dl_values[0] == DLEntry1 + assert dl_values[1] == DLEntry2 + assert dl_values[2] == DLEntry1 + assert dl_values[3] == DLEntry4 + assert dl_values[4] == DLEntry5 + + @raises(ValueError) + def test_replace_key_check(self): + dl = DependenciesList() + dl.replace(object(), object()) diff -Nru turbogears2-2.3.7/tests/test_errorware.py turbogears2-2.3.12/tests/test_errorware.py --- turbogears2-2.3.7/tests/test_errorware.py 2014-10-03 19:43:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_errorware.py 2018-04-06 11:31:55.000000000 +0000 @@ -18,6 +18,15 @@ assert app.__class__.__name__ == self.middleware_name assert not app.reporters + def test_enable_false(self): + app = ErrorReporter(simple_app, {}, enable=False) + assert app.__class__.__name__ != self.middleware_name + + def test_enable_true(self): + app = ErrorReporter(simple_app, {}, enable=True) + assert app.__class__.__name__ == self.middleware_name + assert not app.reporters + def test_enable_email(self): app = ErrorReporter(simple_app, {}, error_email='user@somedomain.com') diff -Nru turbogears2-2.3.7/tests/test.html turbogears2-2.3.12/tests/test.html --- turbogears2-2.3.7/tests/test.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + + + Welcome to TurboGears 2.0 - standing on the shoulders of giants + since 2007 + + + +

All objects from locals():

+ +
+ ${item}: ${repr(locals()['data'][item])}
+ + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_jsonify.py turbogears2-2.3.12/tests/test_jsonify.py --- turbogears2-2.3.7/tests/test_jsonify.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_jsonify.py 2018-04-06 11:31:55.000000000 +0000 @@ -33,12 +33,24 @@ encoded = jsonify.encode(d) assert encoded == '["a", 1, "b", 2]' +def test_list_allowed_iter(): + lists_encoder = jsonify.JSONEncoder(allow_lists=True) + d = ['a', 1, 'b', 2] + encoded = jsonify.encode(d, lists_encoder) + assert encoded == '["a", 1, "b", 2]' + @raises(jsonify.JsonEncodeError) def test_list_iter(): d = list(range(3)) encoded = jsonify.encode_iter(d) assert ''.join(jsonify.encode_iter(d)) == jsonify.encode(d) +def test_list_allowed_iter(): + lists_encoder = jsonify.JSONEncoder(allow_lists=True) + d = list(range(3)) + encoded = jsonify.encode_iter(d, lists_encoder) + assert ''.join(encoded) == '[0, 1, 2]' + def test_dictionary(): d = {'a': 1, 'b': 2} encoded = jsonify.encode(d) @@ -99,6 +111,20 @@ loaded_date = json.loads(encoded) assert len(loaded_date['date'].split('-')) == 3 +def test_datetime_time(): + d = datetime.utcnow().time() + encoded = jsonify.encode({'date':d}) + assert str(d.hour) in encoded, (str(d), encoded) + +def test_datetime_time_iso(): + isodates_encoder = jsonify.JSONEncoder(isodates=True) + + d = datetime.utcnow().time() + encoded = jsonify.encode({'date': d}, encoder=isodates_encoder) + + isoformat_without_millis = json.dumps({'date': d.isoformat()[:8]}) + assert isoformat_without_millis == encoded, (isoformat_without_millis, encoded) + def test_decimal(): d = Decimal('3.14') encoded = jsonify.encode({'dec':d}) diff -Nru turbogears2-2.3.7/tests/test_jsonify_sqlalchemy.py turbogears2-2.3.12/tests/test_jsonify_sqlalchemy.py --- turbogears2-2.3.7/tests/test_jsonify_sqlalchemy.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_jsonify_sqlalchemy.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,6 +1,8 @@ from nose.tools import assert_raises from tg import jsonify import json +import datetime + try: try: @@ -8,7 +10,7 @@ except: import pysqlite2 from sqlalchemy import (MetaData, Table, Column, ForeignKey, - Integer, String) + Integer, String, DateTime, Date, Time) from sqlalchemy.orm import create_session, mapper, relation metadata = MetaData('sqlite:///:memory:') @@ -30,6 +32,12 @@ Column('id', Integer, primary_key=True), Column('val', String(8))) + test5 = Table('test5', metadata, + Column('id', Integer, primary_key=True), + Column('val', String(8)), + Column('date', DateTime()), + Column('time', Time())) + metadata.create_all() class Test2(object): @@ -50,11 +58,17 @@ pass mapper(Test4, test4) + class Test5(object): + pass + mapper(Test5, test5) + test1.insert().execute({'id': 1, 'val': 'bob'}) test2.insert().execute({'id': 1, 'test1id': 1, 'val': 'fred'}) test2.insert().execute({'id': 2, 'test1id': 1, 'val': 'alice'}) test3.insert().execute({'id': 1, 'val': 'bob'}) test4.insert().execute({'id': 1, 'val': 'alberto'}) + test5.insert().execute({'id': 1, 'val': 'sometime', 'time': datetime.time(21, 20, 19), + 'date': datetime.datetime(2016, 12, 11, 10, 9, 8)}) except ImportError: from warnings import warn @@ -109,3 +123,12 @@ jsonify.encode(t) s.expunge(t) assert_raises(ValueError, lambda: jsonify.encode(t)) + + def test_select_rows_datetime(): + s = create_session() + t = test5.select().execute() + encoded = jsonify.encode(dict(results=t), encoder=jsonify.JSONEncoder(isodates=True)) + expected = """{"results": {"count": -1, "rows": [{"count": 1, "rows": {"date": "2016-12-11T10:09:08", "id": 1, "val": "sometime", "time": "21:20:19"}}]}}""" + encoded = json.loads(encoded) + expected = json.loads(expected) + assert encoded == expected, encoded diff -Nru turbogears2-2.3.7/tests/test_render.py turbogears2-2.3.12/tests/test_render.py --- turbogears2-2.3.7/tests/test_render.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_render.py 2018-04-06 11:31:55.000000000 +0000 @@ -2,6 +2,7 @@ Testing for TG2 Configuration """ from nose.tools import raises +from webtest import TestApp import tg from tg.render import MissingRendererError, _get_tg_vars @@ -33,6 +34,16 @@ tg.render_template({}, 'gensh') + +def test_render_default(): + conf = AppConfig(minimal=True) + conf.default_renderer = 'json' + app = conf.make_wsgi_app() + + res = tg.render_template({'value': 'value'}) + assert 'value": "value' in res + + def test_jinja_lookup_nonexisting_template(): conf = AppConfig(minimal=True) conf.use_dotted_templatenames = True @@ -49,6 +60,41 @@ except TemplateNotFound: pass + +class TestKajikiSupport(object): + def setup(self): + conf = AppConfig(minimal=True) + conf.use_dotted_templatenames = True + conf.renderers.append('kajiki') + conf.package = FakePackage() + self.conf = conf + self.app = TestApp(conf.make_wsgi_app()) + self.render = self.conf.render_functions['kajiki'] + + def test_template_found(self): + with test_context(self.app): + res = self.render('tests.test_stack.rendering.templates.kajiki_i18n', {}) + assert 'Your application is now running' in res + + def test_dotted_template_not_found(self): + try: + with test_context(self.app): + res = self.render('tests.test_stack.rendering.templates.this_doesnt_exists', {}) + except IOError as e: + assert 'this_doesnt_exists.xhtml not found' in str(e) + else: + raise AssertionError('Should have raised IOError') + + def test_filename_template_not_found(self): + try: + with test_context(self.app): + res = self.render('this_doesnt_exists/this_doesnt_exists.xhtml', {}) + except IOError as e: + assert 'this_doesnt_exists.xhtml not found in template paths' in str(e) + else: + raise AssertionError('Should have raised IOError') + + class TestMakoLookup(object): def setup(self): conf = AppConfig(minimal=True) @@ -60,7 +106,7 @@ def test_adjust_uri(self): render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader assert mlookup.adjust_uri('this_template_should_pass_unaltered', None) == 'this_template_should_pass_unaltered' @@ -81,7 +127,7 @@ t = Template('Hi') render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader mlookup.template_cache['hi_template'] = t assert mlookup.get_template('hi_template') is t @@ -91,20 +137,20 @@ t = Template('Hi', filename='deleted_template.mak') render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader mlookup.template_cache['deleted_template'] = t mlookup.get_template('deleted_template') @raises(IOError) def test_never_existed(self): render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader mlookup.get_template('deleted_template') def test__check_should_reload_on_cache_expire(self): render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader template_path = mlookup.adjust_uri('tests.test_stack.rendering.templates.mako_inherits_local', None) t = mlookup.get_template(template_path) #cache the template @@ -128,7 +174,7 @@ def test__check_should_not_reload_when_disabled(self): render_mako = self.conf.render_functions['mako'] - mlookup = render_mako.loader + mlookup = render_mako.dotted_loader mlookup.auto_reload = False template_path = mlookup.adjust_uri('tests.test_stack.rendering.templates.mako_inherits_local', None) diff -Nru turbogears2-2.3.7/tests/test_rest_controller_dispatch.py turbogears2-2.3.12/tests/test_rest_controller_dispatch.py --- turbogears2-2.3.7/tests/test_rest_controller_dispatch.py 2014-10-03 19:43:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_rest_controller_dispatch.py 2018-04-06 11:31:55.000000000 +0000 @@ -420,6 +420,10 @@ TestWSGIController.__init__(self, *args, **kargs) self.app = make_app(BasicTGController) + def test_options(self): + r = self.app.options('/rest/') + assert r.status_int == 204 + def test_post(self): r = self.app.post('/rest/') assert 'rest post' in r, r diff -Nru turbogears2-2.3.7/tests/test_stack/baseutils.py turbogears2-2.3.12/tests/test_stack/baseutils.py --- turbogears2-2.3.7/tests/test_stack/baseutils.py 2015-04-28 20:01:51.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/baseutils.py 2018-04-06 11:31:55.000000000 +0000 @@ -26,15 +26,6 @@ 'i18n.lang': None } -class FakeRoutes(object): - def __init__(self, app): - self.app = app - - def __call__(self, environ, start_response): - environ['wsgiorg.routing_args'] = [None, {'controller':'root'}] - environ['routes.url'] = None - return self.app(environ, start_response) - class ControllerWrap(object): def __init__(self, controller): diff -Nru turbogears2-2.3.7/tests/test_stack/dispatch/controllers/root.py turbogears2-2.3.12/tests/test_stack/dispatch/controllers/root.py --- turbogears2-2.3.7/tests/test_stack/dispatch/controllers/root.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/dispatch/controllers/root.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import json + import tg from tg.controllers import TGController from tg.decorators import expose, validate, https, variable_decode, with_trailing_slash, \ @@ -98,6 +100,22 @@ return "Main Default Page called for url /%s"%remainder @expose() + def return_something(self): + return 5 + + @expose() + def return_none(self): + return None + + @expose() + def return_modified_response(self): + tg.response.status_int = 201 + tg.response.content_type = 'application/json' + tg.response.charset = 'utf-8' + tg.response.body = json.dumps({'text': 'Hello World'}).encode('utf-8') + return tg.response + + @expose() def feed(self, feed=None): return feed @@ -174,10 +192,6 @@ def stacked_expose(self, tg_format=None): return dict(got_json=True) - @expose('json') - def json_return_list(self): - return [1,2,3] - @expose(content_type='image/png') def custom_content_type(self): return b'PNG' diff -Nru turbogears2-2.3.7/tests/test_stack/dispatch/templates/echo.mak turbogears2-2.3.12/tests/test_stack/dispatch/templates/echo.mak --- turbogears2-2.3.7/tests/test_stack/dispatch/templates/echo.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/dispatch/templates/echo.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +

${tmpl_context.echo}

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/dispatch/test_url_dispatch.py turbogears2-2.3.12/tests/test_stack/dispatch/test_url_dispatch.py --- turbogears2-2.3.7/tests/test_stack/dispatch/test_url_dispatch.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/dispatch/test_url_dispatch.py 2018-04-06 11:31:55.000000000 +0000 @@ -170,14 +170,7 @@ def test_ignore_parameters(): resp = app.get("/check_params?ignore='bar'&ignore_me='foo'") - assert "None Received" - -def test_json_return_list(): - try: - resp = app.get("/json_return_list") - assert False - except Exception as e: - assert 'You may not expose with JSON a list' in str(e) + assert "None recieved" in resp.text, resp.text def test_https_redirect(): resp = app.get("/test_https?foo=bar&baz=bat") @@ -185,6 +178,24 @@ assert resp.location.endswith("/test_https?foo=bar&baz=bat") resp = app.post("/test_https?foo=bar&baz=bat", status=405) +def test_return_non_string(): + try: + resp = app.get("/return_something") + assert False + except: + # Do not try to be too smart catching all cases + # if returned value is not a valid wsgi app_iter + # let it crash + pass + +def test_return_none(): + resp = app.get('/return_none', status=204) + assert 'Content-Type' not in str(resp), resp + +def test_return_modified_response(): + resp = app.get('/return_modified_response', status=201) + assert 'Hello World' in resp.text + class TestVisits(object): def test_visit_path_sub1(self): resp = app.get("/sub/hitme") Binary files /tmp/tmpp4ByTj/g20I4JyGJt/turbogears2-2.3.7/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.mo and /tmp/tmpp4ByTj/Yy55thbnMQ/turbogears2-2.3.12/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.mo differ diff -Nru turbogears2-2.3.7/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.po turbogears2-2.3.12/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.po --- turbogears2-2.3.7/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.po 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/i18n/de/LC_MESSAGES/tests.test_stack.po 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,19 @@ +# German translations for tests. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: tests 0.0\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2009-07-07 07:07+0700\n" +"PO-Revision-Date: 2009-07-07 07:17+0700\n" +"Last-Translator: FULL NAME \n" +"Language-Team: de \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" + +#: test_stack/rendering/templates/jinja_i18n.jinja:1 +msgid "Your application is now running" +msgstr "Ihre Anwendung läuft jetzt einwandfrei" diff -Nru turbogears2-2.3.7/tests/test_stack/__init__.py turbogears2-2.3.12/tests/test_stack/__init__.py --- turbogears2-2.3.7/tests/test_stack/__init__.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/__init__.py 2018-04-06 11:31:55.000000000 +0000 @@ -44,7 +44,7 @@ for key, value in values.items(): setattr(self, key, value) - def add_debugger_middleware(self, global_conf, app): + def _add_debugger_middleware(self, app_config, app): return app def app_from_config(base_config, deployment_config=None, reset_milestones=True): diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/controllers/root.py turbogears2-2.3.12/tests/test_stack/rendering/controllers/root.py --- turbogears2-2.3.7/tests/test_stack/rendering/controllers/root.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/controllers/root.py 2018-04-06 11:31:55.000000000 +0000 @@ -9,6 +9,8 @@ from tg.render import _get_tg_vars, cached_template import datetime +from webob.exc import HTTPForbidden + if not PY3: from tw.forms import TableForm, TextField, CalendarDatePicker, SingleSelectField, TextArea from tw.api import WidgetsList @@ -110,16 +112,34 @@ def data(self, *args, **kw): return super(SubClassingController, self).data(*args, **kw) + +class ErrorController(TGController): + @expose('genshi:index.html') + def document(self, *args, **kwargs): + return dict() + + class RootController(TGController): j = JsonController() sub1 = SubClassableController() sub2 = SubClassingController() + error = ErrorController() @expose('genshi:index.html') def index(self): return {} + @expose('json') + def aborted_json(self): + raise HTTPForbidden(json_body={'error': 'value'}) + + @expose('kajiki:tests.test_stack.rendering.templates.index') + @expose('json') + def according_to_content_type(self, ctype): + tg.response.content_type = ctype + return dict(value='SomeValue') + @expose('genshi:genshi_doctype.html') def auto_doctype(self): return {} @@ -305,6 +325,13 @@ def index_dotted(self): return {} + @expose('kajiki:tests.test_stack.rendering.templates.index!html') + def index_dotted_with_forced_extension(self): + # This explicitly exposes a Genshi template through Kajiki + # using the !ext syntax to force correct extension resolution. + tmpl_context.now = lambda: 'IT WORKS' + return {} + @expose('genshi:tests.test_stack.rendering.templates.genshi_inherits') def genshi_inherits_dotted(self): return {} @@ -453,6 +480,10 @@ def get_jsonp(self, **kwargs): return {'value': 5} + @expose('jsonp', render_params={'callback_param': 'call', 'key': 'result'}) + def get_jsonp_with_key(self, **kwargs): + return {'result': {'value': 5}} + @expose('json') def get_json_isodates_default(self, **kwargs): return {'date': datetime.datetime.utcnow()} @@ -465,6 +496,14 @@ def get_json_isodates_off(self, **kwargs): return {'date': datetime.datetime.utcnow()} + @expose('json', render_params={'allow_lists': True, 'key': 'values'}) + def get_json_list(self, **kwargs): + return dict(values=[1, 2, 3]) + + @expose('json', render_params={'allow_lists': False, 'key': 'values'}) + def json_return_list(self): + return dict(values=[1,2,3]) + @expose('json') @decode_params('json') def echo_json(self, **kwargs): diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_custom_format.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_custom_format.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_custom_format.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_custom_format.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,19 @@ + + + + + + + Custom Format HTML + + + +
    +
  • Status: ${status}
  • +
  • Format: ${format}
  • +
+ + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_doctype.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_doctype.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_doctype.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_doctype.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,15 @@ + + + + TurboGears 2 doctype generation test + + + +

TurboGears 2 is a rapid web application development toolkit.

+
+

This is a Genshi template.

+ + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_foreign.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_foreign.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_foreign.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_foreign.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,15 @@ + + + + + Foreign Cuisine + + +

Dish of the day

+

Crème brûlée with Käsebrötchen

+

Bon Appétit!

+ + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_form.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_form.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_form.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_form.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,11 @@ + + + + + +

${form.display()}.

+ + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,14 @@ + + + + + + +
+

Inheritance template

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits_sub_dotted.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits_sub_dotted.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits_sub_dotted.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits_sub_dotted.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,15 @@ + + + + + + + +
+

Inheritance template

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits_sub.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits_sub.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_inherits_sub.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_inherits_sub.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,15 @@ + + + + + + + +
+

Inheritance template

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_master.html turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_master.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/genshi_master.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/genshi_master.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + Your title goes here + + + + + +
+

Master template

+
+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/index.html turbogears2-2.3.12/tests/test_stack/rendering/templates/index.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/index.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/index.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,20 @@ + + + + + Welcome to TurboGears 2.0, standing on the + shoulders of giants, since 2007 + + + +
+

TurboGears 2 is rapid web application development toolkit designed to make your life easier.

+
+
+ NOW: ${tmpl_context.now()} +
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/index.xhtml turbogears2-2.3.12/tests/test_stack/rendering/templates/index.xhtml --- turbogears2-2.3.7/tests/test_stack/rendering/templates/index.xhtml 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/index.xhtml 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,17 @@ + + + + + Welcome to TurboGears 2.0, standing on the + shoulders of giants, since 2007 + + + +
+

TurboGears 2 is rapid web application development toolkit designed to make your life easier.

+

${value}

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_autoload.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_autoload.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_autoload.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_autoload.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,18 @@ +{% extends "jinja_base.jinja" %} + +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ {{ "Hello Jinja!"|polluting_function }} +

+{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_base.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_base.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_base.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_base.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,19 @@ + + + + + {% block title %}{% endblock %} - My Webpage + {% block html_head %}{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ + + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_buildins.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_buildins.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_buildins.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_buildins.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,18 @@ +{% extends "jinja_base.jinja" %} + +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ {{ "Hello Jinja!"|upper }} +

+{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_extensions.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_extensions.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_extensions.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_extensions.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,24 @@ +{% extends "jinja_base.jinja" %} + +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ {% autoescape true %} + {{ test_autoescape_on }} + {% endautoescape %} + + {% autoescape false %} + {{ test_autoescape_off }} + {% endautoescape %} +

+{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_filters.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_filters.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_filters.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_filters.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,19 @@ +{% extends "jinja_base.jinja" %} + +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ {{ "Hello Jinja!"|codify }} +

+ {{ "Some Title"|title }} +{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_i18n.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_i18n.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_i18n.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_i18n.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1 @@ +{% trans %}Your application is now running{% endtrans %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_inherits_dotted.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_inherits_dotted.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_inherits_dotted.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_inherits_dotted.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,18 @@ +{% extends "tests.test_stack.rendering.templates.jinja_base" %} +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ Welcome on my awsome homepage. + Use some template context variables: {{tg.url('/')}}, {{N_('fake translation')}} +

+{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_inherits.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_inherits.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_inherits.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_inherits.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,18 @@ +{% extends "jinja_base.jinja" %} +{% block title %}Index{% endblock %} + +{% block html_head %} + +{% endblock %} + +{% block content %} +

Index

+

+ Welcome on my awsome homepage. + Use some template context variables: {{tg.url('/')}}, {{N_('fake translation')}} +

+{% endblock %} diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_noop.jinja turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_noop.jinja --- turbogears2-2.3.7/tests/test_stack/rendering/templates/jinja_noop.jinja 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/jinja_noop.jinja 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1 @@ +

move along, nothing to see here

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/kajiki_i18n.xhtml turbogears2-2.3.12/tests/test_stack/rendering/templates/kajiki_i18n.xhtml --- turbogears2-2.3.7/tests/test_stack/rendering/templates/kajiki_i18n.xhtml 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/kajiki_i18n.xhtml 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1 @@ +

Your application is now running

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/kajiki_paginated.xhtml turbogears2-2.3.12/tests/test_stack/rendering/templates/kajiki_paginated.xhtml --- turbogears2-2.3.7/tests/test_stack/rendering/templates/kajiki_paginated.xhtml 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/kajiki_paginated.xhtml 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,22 @@ + + + +Pagination Test + + + +
+ + +
+ + +
+ +
+ +
+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_base.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_base.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_base.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_base.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + + + + ${self.head_tags()} + + +

Inside parent template

+ ${self.body()} + + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_custom_format.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_custom_format.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_custom_format.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_custom_format.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,5 @@ + + + ${status} + ${format} + \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits_dotted.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits_dotted.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits_dotted.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits_dotted.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +<%inherit file="tests.test_stack.rendering.templates.mako_base" /> + +<%def name="head_tags()"> + + + +

New Page

+ +

inherited mako page

diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits_local.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits_local.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits_local.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits_local.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +<%inherit file="local:test_stack.rendering.templates.mako_base" /> + +<%def name="head_tags()"> + + + +

New Page

+ +

inherited mako page

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_inherits.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_inherits.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +<%inherit file="/mako_base.mak" /> + +<%def name="head_tags()"> + + + +

New Page

+ +

inherited mako page

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_noop.mak turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_noop.mak --- turbogears2-2.3.7/tests/test_stack/rendering/templates/mako_noop.mak 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/mako_noop.mak 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +

This is the mako index page

\ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/frombottom_dotted.html turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/frombottom_dotted.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/frombottom_dotted.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/frombottom_dotted.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,21 @@ + + + + + + Welcome to TurboGears 2.0, standing on the + shoulders of giants, since 2007 + + + +

+ from sub-template: sub.frombottom_dotted +

+
+

TurboGears 2 is rapid web application development toolkit designed to make your life easier.

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/frombottom.html turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/frombottom.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/frombottom.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/frombottom.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,21 @@ + + + + + + Welcome to TurboGears 2.0, standing on the + shoulders of giants, since 2007 + + + +

+ from sub-template: sub.frombottom +

+
+

TurboGears 2 is rapid web application development toolkit designed to make your life easier.

+
+ + diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/tobeincluded.html turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/tobeincluded.html --- turbogears2-2.3.7/tests/test_stack/rendering/templates/sub/tobeincluded.html 1970-01-01 00:00:00.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/templates/sub/tobeincluded.html 2018-04-06 11:31:55.000000000 +0000 @@ -0,0 +1,3 @@ +

+ from sub-template: sub.tobeincluded +

diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/test_decorators.py turbogears2-2.3.12/tests/test_stack/rendering/test_decorators.py --- turbogears2-2.3.7/tests/test_stack/rendering/test_decorators.py 2015-04-28 20:01:51.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/test_decorators.py 2018-04-06 11:31:55.000000000 +0000 @@ -58,6 +58,10 @@ resp = self.app.post_json('/echo_json', params) assert resp.json_body == params + def test_decode_params_json_invalid(self): + resp = self.app.get('/echo_json', headers={'Content-Type': 'application/json'}) + assert resp.json_body == {} + @raises(ValueError) def test_decode_params_notjson(self): @decode_params('xml') diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/test_pagination.py turbogears2-2.3.12/tests/test_stack/rendering/test_pagination.py --- turbogears2-2.3.7/tests/test_stack/rendering/test_pagination.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/test_pagination.py 2018-04-06 11:31:55.000000000 +0000 @@ -285,7 +285,7 @@ if ming is None: raise SkipTest('Ming not available...') - cls.basic_session = Session(create_datastore('mim:///')) + cls.basic_session = Session(create_datastore('mim:///testdb')) cls.s = ODMSession(cls.basic_session) class Author(MappedClass): diff -Nru turbogears2-2.3.7/tests/test_stack/rendering/test_rendering.py turbogears2-2.3.12/tests/test_stack/rendering/test_rendering.py --- turbogears2-2.3.7/tests/test_stack/rendering/test_rendering.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/rendering/test_rendering.py 2018-04-06 11:31:55.000000000 +0000 @@ -633,6 +633,19 @@ resp = app.get('/get_tg_vars') assert 'inject_this_var' in resp +def test_index_dotted_with_forced_extension(): + app = setup_noDB() + resp = app.get('/index_dotted_with_forced_extension') + assert 'Welcome to TurboGears' in resp + assert 'NOW: IT WORKS' in resp + + # The purely kajiki version of this template doesn't have NOW, + # check it's actually like that otherwise we would pass the previous + # check even when it didn't resolve to the genshi template. + kresp = app.get('/kajiki_index_dotted') + assert 'Welcome to TurboGears' in kresp + assert 'NOW: IT WORKS' not in kresp + def test_render_hooks(): old_hooks, tg.hooks = tg.hooks, _TGGlobalHooksNamespace() @@ -660,6 +673,24 @@ finally: tg.hooks = old_hooks + +class TestEngineDetection(object): + def setUp(self): + self.app = setup_noDB(genshi_doctype='html', extra={ + 'errorpage.enabled': True + }) + + def test_no_engine_for_content_type(self): + resp = self.app.get('/aborted_json', status=403) + assert '{"error":"value"}' in resp + + def test_content_type_provided(self): + resp = self.app.get('/according_to_content_type?ctype=text/html') + assert '

SomeValue' in resp + resp = self.app.get('/according_to_content_type?ctype=application/json') + assert 'value": "SomeValue' in resp, resp + + class TestTemplateCaching(object): def setUp(self): base_config = TestConfig(folder='rendering', values={ @@ -722,6 +753,10 @@ resp = self.app.get('/get_jsonp', params={'call': 'callme'}) assert 'callme({"value": 5});' in resp.text, resp + def test_jsonp_with_key(self): + resp = self.app.get('/get_jsonp_with_key', params={'call': 'callme'}) + assert 'callme({"value": 5});' in resp.text, resp + def test_jsonp_missing_callback(self): resp = self.app.get('/get_jsonp', status=400) assert 'JSONP requires a "call" parameter with callback name' in resp.text, resp @@ -739,3 +774,14 @@ def test_json_without_isodates(self): resp = self.app.get('/get_json_isodates_off') assert ' ' in resp.json_body['date'], resp + + def test_json_lists_allowing(self): + resp = self.app.get('/get_json_list') + assert '[1, 2, 3]' == resp.text + + def test_json_return_list(self): + try: + resp = self.app.get("/json_return_list") + assert False + except Exception as e: + assert 'Your Encoded object must be dict-like' in str(e), e diff -Nru turbogears2-2.3.7/tests/test_stack/test_authz.py turbogears2-2.3.12/tests/test_stack/test_authz.py --- turbogears2-2.3.7/tests/test_stack/test_authz.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_stack/test_authz.py 2018-04-06 11:31:55.000000000 +0000 @@ -28,7 +28,7 @@ from tg.support.middlewares import CacheMiddleware, SessionMiddleware, StatusCodeRedirect from tg.decorators import Decoration -from .baseutils import ControllerWrap, FakeRoutes, default_config +from .baseutils import ControllerWrap, default_config from ..base import make_app as base_make_app from tg.configuration.auth import setup_auth, TGAuthMetadata @@ -197,6 +197,15 @@ return {'key': 'value'} @expose() + def passthrough_abort(self): + abort(403, passthrough='json') + + @expose() + def passthrough_explicit(self): + request.disable_auth_challenger() + abort(403) + + @expose() @require(in_group('managers')) @require(has_permission('commit')) def force_commit(self): @@ -394,6 +403,13 @@ assert resp.status == '200 OK', 'Expected 200, got %s' % (resp.body) assert {'key': 'value'} == resp.json, resp.json + def test_auth_passthrough(self): + resp = self.app.get('/passthrough_abort', status=403) + assert '"Access was denied to this resource."' in resp, resp + + resp = self.app.get('/passthrough_explicit', status=403) + assert 'Access was denied to this resource' in resp, resp + class TestAllowOnlyDecoratorInSubController(BaseIntegrationTests): """Test case for the @allow_only decorator in a sub-controller""" diff -Nru turbogears2-2.3.7/tests/test_tg_controller_dispatch.py turbogears2-2.3.12/tests/test_tg_controller_dispatch.py --- turbogears2-2.3.7/tests/test_tg_controller_dispatch.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_tg_controller_dispatch.py 2018-04-06 11:31:55.000000000 +0000 @@ -147,6 +147,10 @@ def get_controller_state(self): return '/'.join([p[0] for p in tg.request.controller_state.controller_path]) + @expose() + def get_dispatch_state(self): + return '/'.join([p[0] for p in tg.request.dispatch_state.controller_path]) + class SubController3(object): @expose() def get_all(self): @@ -365,6 +369,13 @@ return "Main default page called for url /%s" % [str(r) for r in remainder] @expose() + def response_responded(self): + tg.response.body = b'Body Response' + tg.response.content_type = 'text/plain' + tg.response.charset = 'utf-8' + return tg.response + + @expose() def feed(self, feed=None): return feed @@ -504,8 +515,6 @@ return str(tg.request._controller_state.routing_args) @expose('json') - @expose('genshi') - @expose() def get_response_type(self): return dict(ctype=tg.request.response_type) @@ -513,6 +522,7 @@ def hello_ext(self, *args): return str(tg.request.response_ext) + class TestNotFoundController(TestWSGIController): def __init__(self, *args, **kargs): @@ -602,6 +612,14 @@ assert 'b' in str(r) assert 'c' in str(r) + def test_unexpected_arguments_are_discarded(self): + r = self.app.get('/hello/YourName/silly?unexpected=1&more=1') + assert 'Hello YourName' in r, r + + def test_named_arguments_override_positional(self): + r = self.app.get('/hello/YourName/silly?name=You&more=1') + assert 'Hello You' in r, r + def test_response_without_charset(self): r = self.app.get('/index_unicode') assert 'Hello World' in r, r @@ -859,8 +877,10 @@ assert "('a', 'b', {})" in resp.body.decode('utf-8'), resp def test_controller_state(self): - resp = self.app.get('/sub/get_controller_state') + resp_deprecated = self.app.get('/sub/get_controller_state') + resp = self.app.get('/sub/get_dispatch_state') assert '/sub' in resp + assert resp_deprecated.text == resp.text def test_response_type_json(self): resp = self.app.get('/get_response_type.json') @@ -896,6 +916,10 @@ resp = self.app.get('/sub3/controller_url/true/a/b/c') assert resp.text == 'sub3/controller_url', resp.text + def test_responded_response(self): + resp = self.app.get('/response_responded') + assert resp.text == 'Body Response', resp.text + class TestNestedWSGIAppWithoutSeekable(TestWSGIController): def setUp(self, *args, **kargs): diff -Nru turbogears2-2.3.7/tests/test_util.py turbogears2-2.3.12/tests/test_util.py --- turbogears2-2.3.7/tests/test_util.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tests/test_util.py 2018-04-06 11:31:55.000000000 +0000 @@ -4,13 +4,15 @@ import tg from tg.util import * from tg.configuration.utils import get_partial_dict -from nose.tools import eq_, raises +from nose.tools import eq_, raises, assert_raises import os from tg.controllers.util import * from tg.util.dates import get_fixed_timezone, utctz, parse_datetime from tg.util.files import safe_filename from tg.util.html import script_json_encode +from tg.util.misc import unless from tg.util.webtest import test_context +from tg.validation import Convert, TGValidationError from tg.wsgiapp import TemplateContext, AttribSafeTemplateContext import tg._compat @@ -86,6 +88,11 @@ res = url('.', {'p1': SubException('a', 'b', 'c')}) assert res == '.?p1=a+b+c', res + def test_url_https(self): + with test_context(None, '/index'): + res = url('/users', qualified=True, scheme='https') + assert res == 'https://localhost/users', res + class TestBunch(object): def test_add_entry(self): @@ -121,6 +128,17 @@ def test_local_file_returns_absolute_path(self): assert os.path.isabs(DottedFileNameFinder.lookup('this_should_be_my_template')) + def test_load_from_zipped_egg(self): + import sys + eggfile = os.path.join(os.path.dirname(__file__), 'fixtures', 'fakepackage.zip') + sys.path.append(eggfile) + + tmplf = DottedFileNameFinder().get_dotted_filename('fakepackage.test_template', + template_extension='.xhtml') + with open(tmplf) as t: + template = t.read() + assert template == '

Your application is now running

', template + class TestLazyString(object): def test_lazy_string_to_str(self): @@ -136,6 +154,16 @@ lf = l.format('HI') assert lf == 'HI', lf + def test_lazy_string_with_genshi(self): + # See https://github.com/TurboGears/tg2/pull/68 + from genshi.template.markup import MarkupTemplate + markup = """${foo}""" + template = MarkupTemplate(markup) + stream = template.generate(foo=LazyString(lambda: "bar")) + output = str(stream) # Contains only ascii char, so it should cast fine on both py2 and py3 + assert output == 'bar', output + + class TestAttribSafeContextObj(object): def setup(self): self.c = AttribSafeTemplateContext() @@ -245,6 +273,10 @@ rv = "" % script_json_encode({'x': ["foo", "bar", "baz'"]}) assert rv == '' + def test_script_json_encode_array(self): + rv = "" % script_json_encode(['1', 2, 5]) + assert rv == '', rv + class TestFilesUtils(object): def test_safe_filename(self): @@ -292,4 +324,45 @@ with test_context(app): context._pop_object() # Check that config got cleaned up even though context caused an exception - assert not config._object_stack() \ No newline at end of file + assert not config._object_stack() + + +class TestMiscUtils(object): + def test_unless(self): + not5 = unless(lambda x: x % 5, 0) + assert not5(6) == 1 + + assert_raises(ValueError, not5, 10) + + def test_unless_sqla(self): + from sqlalchemy import (MetaData, Table, Column, Integer, String) + from sqlalchemy.orm import create_session, mapper + + metadata = MetaData('sqlite:///:memory:') + testtable = Table('test1', metadata, + Column('id', Integer, primary_key=True), + Column('val', String(8))) + metadata.create_all() + + class Test(object): + pass + mapper(Test, testtable) + + testtable.insert().execute({'id': 1, 'val': 'bob'}) + testtable.insert().execute({'id': 2, 'val': 'bobby'}) + testtable.insert().execute({'id': 3, 'val': 'alberto'}) + + sess = create_session() + getunless = unless(sess.query(Test).get) + + x = getunless(1) + assert x.val == 'bob', x + + x = getunless(2) + assert x.val == 'bobby', x + + assert_raises(ValueError, getunless, 5) + assert_raises(TGValidationError, Convert(getunless).to_python, '5') + + x = Convert(getunless).to_python('1') + assert x.val == 'bob', x \ No newline at end of file diff -Nru turbogears2-2.3.7/tests/test_validation.py turbogears2-2.3.12/tests/test_validation.py --- turbogears2-2.3.7/tests/test_validation.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tests/test_validation.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import json from functools import partial from nose.tools import raises from nose import SkipTest @@ -23,6 +24,7 @@ import tw2.core as tw2c import tw2.forms as tw2f + class MovieForm(tw2f.TableForm): title = tw2f.TextField(validator=tw2c.Required) year = tw2f.TextField(size=4, validator=tw2c.IntValidator) @@ -346,7 +348,6 @@ return output - @expose(content_type='text/plain') @validate({ 'num': Convert(int, l_('This must be a number')) @@ -361,6 +362,48 @@ def post_pow2_opt(self, num=-1): return str(num*num) + @expose(content_type='text/plain') + @validate({ + 'num': Convert(int, u_('àèìòù')) + }, error_handler=validation_errors_response) + def unicode_error_pow(self, num=-1): + return str(num*num) + + @expose(content_type='text/plain') + @validate({ + 'num': Convert(int, l_(u_('àèìòù'))) + }, error_handler=validation_errors_response) + def lazy_unicode_error_pow(self, num=-1): + return str(num * num) + + @expose(content_type='text/plain') + @validate({ + 'val': Convert(lambda v: int(v) > 0 or int('ERROR')) + }, error_handler=validation_errors_response) + def chain_validation_0(self, val): + return '>0' + + @expose(content_type='text/plain') + @validate({ + 'val': Convert(lambda v: int(v) > 1 or int('ERROR')) + }, error_handler=chain_validation_0, chain_validation=True) + def chain_validation_1(self, val): + return '>1' + + @expose(content_type='text/plain') + @validate({ + 'val': Convert(lambda v: int(v) > 2 or int('ERROR')) + }, error_handler=chain_validation_1, chain_validation=True) + def chain_validation_2(self, val): + return '>2' + + @expose(content_type='text/plain') + @validate({ + 'val': Convert(lambda v: int(v) > 3 or int('ERROR')) + }, error_handler=chain_validation_2, chain_validation=True) + def chain_validation_begin(self, val): + return '>3' + class TestTGController(TestWSGIController): def setUp(self): @@ -646,18 +689,18 @@ def test_convert_validation_fail(self): resp = self.app.post('/post_pow2', {'num': 'HELLO'}, status=412) - assert 'This must be a number' in resp.text, resp + assert 'This must be a number' in resp.json['errors']['num'] def test_convert_validation_missing(self): resp = self.app.post('/post_pow2', {'num': ''}, status=412) - assert 'This must be a number' in resp.text, resp + assert 'This must be a number' in resp.json['errors']['num'] resp = self.app.post('/post_pow2', status=412) - assert 'This must be a number' in resp.text, resp + assert 'This must be a number' in resp.json['errors']['num'] def test_convert_validation_optional(self): resp = self.app.post('/post_pow2_opt', {'num': 'HELLO'}, status=412) - assert 'This must be a number' in resp.text, resp + assert 'This must be a number' in resp.json['errors']['num'] resp = self.app.post('/post_pow2_opt', {'num': '5'}) assert resp.text == '25', resp @@ -667,3 +710,43 @@ resp = self.app.post('/post_pow2_opt') assert resp.text == '0', resp + + def test_validation_errors_unicode(self): + resp = self.app.post('/unicode_error_pow', {'num': 'NOT_A_NUMBER'}, status=412) + assert resp.json['errors']['num'] == u_('àèìòù'), resp.json + + def test_validation_errors_lazy_unicode(self): + resp = self.app.post('/lazy_unicode_error_pow', {'num': 'NOT_A_NUMBER'}, status=412) + assert resp.json['errors']['num'] == u_('àèìòù'), resp.json + + +class TestChainValidation(TestWSGIController): + def setUp(self): + TestWSGIController.setUp(self) + tg.config.update({ + 'paths': {'root': data_dir}, + 'package': tests, + }) + + self.app = make_app(BasicTGController, config_options={ + 'i18n.enabled': True + }) + + def test_no_chain_validation(self): + res = self.app.get('/chain_validation_begin', params={'val': 4}) + self.assertEqual(res.text, '>3') + + res = self.app.get('/chain_validation_begin', params={'val': 3}) + self.assertEqual(res.text, '>2') + + def test_single_chain_validation(self): + res = self.app.get('/chain_validation_begin', params={'val': 2}) + self.assertEqual(res.text, '>1') + + def test_double_chain_validation(self): + res = self.app.get('/chain_validation_begin', params={'val': 1}) + self.assertEqual(res.text, '>0') + + def test_last_chain_validation(self): + res = self.app.get('/chain_validation_begin', params={'val': 0}, status=412) + self.assertEqual(res.json, json.loads('{"errors":{"val":"Invalid"},"values":{"val":"0"}}')) diff -Nru turbogears2-2.3.7/tg/appwrappers/errorpage.py turbogears2-2.3.12/tg/appwrappers/errorpage.py --- turbogears2-2.3.7/tg/appwrappers/errorpage.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/appwrappers/errorpage.py 2018-04-06 11:31:55.000000000 +0000 @@ -15,6 +15,10 @@ - ``errorpage.enabled``: Whenever the custom error page is enabled or not. - ``errorpage.status_codes``: List of HTTP errors that should be trapped. By default 403, 404, 500. + - ``errorpage.content_types``: List of Content-Types for which the custom error + page should be displayed. IE: ``["text/html"]``. An empty list means all. + An entry with value ``None`` means no content type provided. + Default is ``["text/html", None]``. - ``errorpage.handle_exceptions``: Whenever exceptions should be trapped and treated as a 500 error or not. By default this is ``True`` when ``debug=false``. - ``errorapge.path``: Path of the controller should be displayed in case of @@ -28,18 +32,21 @@ 'enabled': False, 'status_codes': tuple(), 'handle_exceptions': not asbool(config.get('debug', False)), - 'path': '/error/document' + 'path': '/error/document', + 'content_types': ["text/html", None] } options.update(coerce_config(config, 'errorpage.', { 'enabled': asbool, 'status_codes': aslist, 'handle_exceptions': asbool, + 'content_types': aslist })) self.handle_error_enabled = options['enabled'] self.handle_status_codes = set(asint(s) for s in options['status_codes']) self.handle_exceptions = options['handle_exceptions'] self.handle_error_path = options['path'] + self.handle_content_types = options['content_types'] if self.handle_exceptions and 500 not in self.handle_status_codes: self.handle_status_codes.add(500) @@ -64,10 +71,16 @@ resp = context.response resp.status_code = 500 + if not environ.get('tg.status_code_redirect', True): + # status_code_redirect disabled per this request + return resp + status_code = resp.status_code - log.debug('ErrorPageApplicationWrapper response: %s -> %s', - environ['PATH_INFO'], status_code) - if status_code in self.handle_status_codes: + content_type = resp.content_type + log.debug('ErrorPageApplicationWrapper response: %s -> %s @ %s', + environ['PATH_INFO'], status_code, content_type) + if status_code in self.handle_status_codes and \ + (not self.handle_content_types or content_type in self.handle_content_types): environ['tg.original_request'] = context.request.copy() environ['tg.original_response'] = resp diff -Nru turbogears2-2.3.7/tg/appwrappers/transaction_manager.py turbogears2-2.3.12/tg/appwrappers/transaction_manager.py --- turbogears2-2.3.7/tg/appwrappers/transaction_manager.py 2015-05-20 10:23:39.000000000 +0000 +++ turbogears2-2.3.12/tg/appwrappers/transaction_manager.py 2018-04-06 11:31:55.000000000 +0000 @@ -23,7 +23,7 @@ - ``tm.attempts``: Number of times the transaction should be retried if it fails (no retry by default) - ``tm.commit_veto``: A function that will be called for every transaction to check - if it should abort transaction or let it go. Function signature shoul be: + if it should abort transaction or let it go. Function signature should be: ``function(environ, status_code, headers) -> bool``. """ @@ -73,15 +73,11 @@ while attempts_left: attempts_left -= 1 + log.debug('Attempts Left %d (%d total)', attempts_left, total_attempts) + txn = transaction_manager.begin() try: - log.debug('Attempts Left %d (%d total)', attempts_left, total_attempts) - transaction_manager.begin() - - t = transaction_manager.get() - t.note(environ.get('PATH_INFO', '')) - response = self.next_handler(controller, environ, context) - if transaction_manager.isDoomed(): + if txn.isDoomed(): log.debug('Transaction doomed') raise AbortTransaction(response) @@ -91,18 +87,18 @@ log.debug('Transaction vetoed') raise AbortTransaction(response) - transaction_manager.commit() + txn.commit() log.debug('Transaction committed!') return response except AbortTransaction as e: - transaction_manager.abort() + txn.abort() return e.response except: exc_info = sys.exc_info() log.debug('Error while running request, aborting transaction') try: can_retry = transaction_manager._retryable(*exc_info[:-1]) - transaction_manager.abort() + txn.abort() if (attempts_left <= 0) or (not can_retry): reraise(*exc_info) finally: diff -Nru turbogears2-2.3.7/tg/_compat.py turbogears2-2.3.12/tg/_compat.py --- turbogears2-2.3.7/tg/_compat.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tg/_compat.py 2018-04-06 11:31:55.000000000 +0000 @@ -39,24 +39,28 @@ def bytes_(s): return str(s) + def im_func(f): if PY3: # pragma: no cover return getattr(f, '__func__', None) else: return getattr(f, 'im_func', None) + def default_im_func(f): if PY3: # pragma: no cover return getattr(f, '__func__', f) else: return getattr(f, 'im_func', f) + def im_self(f): if PY3: # pragma: no cover return getattr(f, '__self__', None) else: return getattr(f, 'im_self', None) + def im_class(f): if PY3: # pragma: no cover self = im_self(f) @@ -67,10 +71,12 @@ else: return getattr(f, 'im_class', None) + def with_metaclass(meta, base=object): """Create a base class with a metaclass.""" return meta("NewBase", (base,), {}) + if PY3: # pragma: no cover import builtins exec_ = getattr(builtins, "exec") @@ -96,3 +102,43 @@ exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) + + +try: + from importlib import import_module +except ImportError: # pragma: no cover + # Compatibility module for python2.6 + import sys + + def _resolve_name(name, package, level): + """Return the absolute name of the module to be imported.""" + if not hasattr(package, 'rindex'): + raise ValueError("'package' not set to a string") + dot = len(package) + for x in xrange(level, 1, -1): + try: + dot = package.rindex('.', 0, dot) + except ValueError: + raise ValueError("attempted relative import beyond top-level " + "package") + return "%s.%s" % (package[:dot], name) + + def import_module(name, package=None): + """Import a module. + + The 'package' argument is required when performing a relative import. It + specifies the package to use as the anchor point from which to resolve the + relative import to an absolute import. + + """ + if name.startswith('.'): + if not package: + raise TypeError("relative imports require the 'package' argument") + level = 0 + for character in name: + if character != '.': + break + level += 1 + name = _resolve_name(name[level:], package, level) + __import__(name) + return sys.modules[name] \ No newline at end of file diff -Nru turbogears2-2.3.7/tg/configuration/app_config.py turbogears2-2.3.12/tg/configuration/app_config.py --- turbogears2-2.3.7/tg/configuration/app_config.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/configuration/app_config.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,11 +1,11 @@ """Configuration Helpers for TurboGears 2""" - import os import logging import warnings from copy import copy, deepcopy import mimetypes from collections import MutableMapping as DictMixin, deque +from tg._compat import import_module from tg.appwrappers.identity import IdentityApplicationWrapper from tg.support.middlewares import StaticsMiddleware, SeekableRequestBodyMiddleware, \ @@ -17,8 +17,8 @@ import tg from tg.util import Bunch, DottedFileNameFinder from tg.configuration import milestones -from tg.configuration.utils import TGConfigError, coerce_config, get_partial_dict, coerce_options - +from tg.configuration.utils import TGConfigError, coerce_config, get_partial_dict, coerce_options, \ + DependenciesList from tg.renderers.genshi import GenshiRenderer from tg.renderers.json import JSONRenderer from tg.renderers.jinja import JinjaRenderer @@ -131,10 +131,10 @@ def __init__(self, controller_wrapper, config, next_wrapper): # Backward compatible old-way of configuring controller wrappers warnings.warn("Controller wrapper will now accept the configuration" - "as parameter when called instead of receiving it as" - "a constructor parameter, please refer to the documentation" - "to update your controller wrappers", - DeprecationWarning, stacklevel=2) + " as parameter when called instead of receiving it as" + " a constructor parameter, please refer to the documentation" + " to update your controller wrappers", + DeprecationWarning, stacklevel=3) def _adapted_next_wrapper(controller, remainder, params): return next_wrapper(tg.config._current_obj(), @@ -158,8 +158,8 @@ overridden by users who wish to have finer grained control over the setup of the WSGI environment in which their application is run. - This is the place to configure custom routes, transaction handling, - error handling, etc. + This is the place to configure your application, database, + transaction handling, error handling, etc. Configuration Options provided: @@ -205,7 +205,7 @@ - ``ming.db`` -> If Database is not provided in ``ming.url`` it can be specified here. - ``ming.connection.*`` -> Options to configure the ming connection, refer to :func:`ming.datastore.create_datastore` for available options. - - ``use_sqlalchemy`` -> Enable/Disable Ming as Models storage. + - ``use_sqlalchemy`` -> Enable/Disable SQLalchemy as Models storage. - ``sqlalchemy.url`` -> Url of the SQLAlchemy database. Refer to :ref:`sqla_master_slave` for configuring master-slave urls. """ @@ -244,7 +244,6 @@ self.rendering_engines_without_vars = set() self.rendering_engines_options = {} - self.enable_routes = False self.enable_routing_args = False self.disable_request_extensions = minimal @@ -273,15 +272,12 @@ self.call_on_shutdown = [] self.controller_caller = call_controller self.controller_wrappers = [] - self.application_wrappers = [] - self.application_wrappers_dependencies = {False: [], - None: [], - True: []} + self.application_wrappers = DependenciesList() - #override this variable to customize how the tw2 middleware is set up + # override this variable to customize how the tw2 middleware is set up self.custom_tw2_config = {} - #This is for minimal mode to set root controller manually + # This is for minimal mode to set root controller manually if root_controller is not None: self['tg.root_controller'] = root_controller @@ -401,8 +397,7 @@ 'milestone has been reached, the wrapper will be used only' 'for future TGApp instances.', wrapper) - self.application_wrappers_dependencies.setdefault(after, []).append(wrapper) - self._configure_application_wrappers() + self.application_wrappers.add(wrapper, after=after) def register_rendering_engine(self, factory): """Registers a rendering engine ``factory``. @@ -465,7 +460,8 @@ if not errorware['debug']: errorware['debug'] = False - trace_errors_config = coerce_config(conf, 'trace_errors.', {'smtp_use_tls': asbool, + trace_errors_config = coerce_config(conf, 'trace_errors.', {'enable': asbool, + 'smtp_use_tls': asbool, 'dump_request_size': asint, 'dump_request': asbool, 'dump_local_frames': asbool, @@ -525,12 +521,6 @@ else: conf['i18n.enabled'] = False - # See http://trac.turbogears.org/ticket/2247 - if conf['debug']: - conf['tg.strict_tmpl_context'] = True - else: - conf['tg.strict_tmpl_context'] = False - # Load conf dict into the global config object try: reqlocal_config.pop_process_config() @@ -596,23 +586,6 @@ log.debug('Disabling Transaction Manager as SQLAlchemy is not available') self.use_transaction_manager = False - def _configure_application_wrappers(self): - # Those are the heads of the dependencies tree - DEPENDENCY_HEADS = (False, None, True) - - # Clear in place, this is to avoid desync between self and config - self.application_wrappers[:] = [] - - registered_wrappers = self.application_wrappers_dependencies.copy() - visit_queue = deque(DEPENDENCY_HEADS) - while visit_queue: - current = visit_queue.popleft() - if current not in DEPENDENCY_HEADS: - self.application_wrappers.append(current) - - dependant_wrappers = registered_wrappers.pop(current, []) - visit_queue.extendleft(reversed(dependant_wrappers)) - def _configure_package_paths(self): try: self.package @@ -647,112 +620,77 @@ the default setting of tg.strict_tmpl_context :: from tg.configuration import AppConfig - from tg import config - - class MyAppConfig(AppConfig): - def after_init_config(self): - config['tg.strict_tmpl_context'] = False - - base_config = MyAppConfig() - - """ - - def setup_routes(self): - """Setup the default TG2 routes - - Override this and setup your own routes maps if you want to use - custom routes. - - It is recommended that you keep the existing application routing in - tact, and just add new connections to the mapper above the routes_placeholder - connection. Lets say you want to add a tg controller SamplesController, - inside the controllers/samples.py file of your application. You would - augment the app_cfg.py in the following way:: - - from routes import Mapper - from tg.configuration import AppConfig class MyAppConfig(AppConfig): - def setup_routes(self): - map = Mapper(directory=config['paths']['controllers'], - always_scan=config['debug']) - - # Add a Samples route - map.connect('/samples/', controller='samples', action=index) - - # Setup a default route for the root of object dispatch - map.connect('*url', controller='root', action='routes_placeholder') - - config['routes.map'] = map - + def after_init_config(self, conf): + conf['tg.strict_tmpl_context'] = False base_config = MyAppConfig() """ - if not self.enable_routes: - return None - from routes import Mapper - - map = Mapper(directory=config['paths']['controllers'], - always_scan=config['debug']) - - # Setup a default route for the root of object dispatch - map.connect('*url', controller='root', action='routes_placeholder') - - config['routes.map'] = map - return map - - def setup_helpers_and_globals(self): - """Add helpers and globals objects to the config. + def _setup_helpers_and_globals(self, conf): + """Add helpers and globals objects to the ``conf``. Override this method to customize the way that ``app_globals`` and ``helpers`` - are setup. + are setup. TurboGears expects them to be available in ``conf`` dictionary + as ``tg.app_globals`` and ``helpers``. """ - - gclass = getattr(self, 'app_globals', None) + # Setup AppGlobals + gclass = conf.pop('app_globals', None) if gclass is None: try: - g = self.package.lib.app_globals.Globals() + gclass = conf['package'].lib.app_globals.Globals except AttributeError: - log.warn('app_globals not provided and lib.app_globals.Globals class is not available.') - g = Bunch() - else: - g = gclass() + pass + + if gclass is None: + try: + app_globals_mod = import_module('.lib.app_globals', package=self.package.__name__) + gclass = getattr(app_globals_mod, 'Globals') + except (ImportError, AttributeError): + pass + + if gclass is None: + log.warn('app_globals not provided and lib.app_globals.Globals is not available.') + gclass = Bunch + + g = gclass() g.dotted_filename_finder = DottedFileNameFinder() - config['tg.app_globals'] = g + conf['tg.app_globals'] = g - if config.get('tg.pylons_compatible', True): - config['pylons.app_globals'] = g + if conf.get('tg.pylons_compatible', True): + conf['pylons.app_globals'] = g - h = getattr(self, 'helpers', None) + # Setup Helpers + h = conf.get('helpers', None) if h is None: try: - h = self.package.lib.helpers + h = conf['package'].lib.helpers except AttributeError: - log.warn('helpers not provided and lib.helpers is not available.') - h = Bunch() - config['helpers'] = h - - def setup_persistence(self): - """Override this method to define how your application configures it's persistence model. - the default is to setup sqlalchemy from the cofiguration file, but you might choose - to set up a persistence system other than sqlalchemy, or add an additional persistence - layer. Here is how you would go about setting up a ming (mongo) persistence layer:: - - class MingAppConfig(AppConfig): - def setup_persistence(self): - self.ming_ds = DataStore(config['mongo.url']) - session = Session.by_name('main') - session.bind = self.ming_ds - """ - if self.use_sqlalchemy: - self.setup_sqlalchemy() - elif self.use_ming: - self.setup_ming() + pass + + if h is None: + try: + h = import_module('.lib.helpers', package=self.package.__name__) + except (ImportError, AttributeError): + pass - def setup_ming(self): + if h is None: + log.warn('helpers not provided and lib.helpers is not available.') + h = Bunch() + + conf['helpers'] = h + + def _setup_persistence(self, conf): + """Configures application persistence model""" + if conf['use_sqlalchemy']: + self._setup_sqlalchemy(conf) + if conf['use_ming']: + self._setup_ming(conf) + + def _setup_ming(self, conf): """Setup MongoDB database engine using Ming""" try: from ming import create_datastore @@ -770,39 +708,44 @@ from pymongo.read_preferences import ReadPreference return getattr(ReadPreference, value) - datastore_options = coerce_config(config, 'ming.connection.', {'max_pool_size':asint, - 'network_timeout':asint, - 'tz_aware':asbool, - 'safe':asbool, - 'journal':asbool, - 'wtimeout':asint, - 'fsync':asbool, - 'ssl':asbool, - 'read_preference':mongo_read_pref}) + datastore_options = coerce_config(conf, 'ming.connection.', {'max_pool_size':asint, + 'network_timeout':asint, + 'tz_aware':asbool, + 'safe':asbool, + 'journal':asbool, + 'wtimeout':asint, + 'fsync':asbool, + 'ssl':asbool, + 'read_preference':mongo_read_pref}) datastore_options.pop('host', None) datastore_options.pop('port', None) - datastore = create_ming_datastore(config['ming.url'], config.get('ming.db', ''), + datastore = create_ming_datastore(conf['ming.url'], + conf.get('ming.db', ''), **datastore_options) - config['tg.app_globals'].ming_datastore = datastore + conf['tg.app_globals'].ming_datastore = datastore try: - package_models = self.package.model + package_models = conf['package'].model except AttributeError: package_models = None - model = getattr(self, 'model', package_models) + model = conf.get('model', package_models) if model is None: raise TGConfigError('Ming enabled, but no models provided') - model.init_model(datastore) + ming_session = model.init_model(datastore) + if ming_session is not None: + # If init_model returns a specific session, keep it around + # as the MongoDB Session. + conf['MingSession'] = ming_session - if not hasattr(self, 'DBSession'): - # If the user hasn't specified a session, assume + if 'DBSession' not in conf: + # If the user hasn't specified a default session, assume # he/she uses the default DBSession in model - self.DBSession = model.DBSession + conf['DBSession'] = model.DBSession - def setup_sqlalchemy(self): + def _setup_sqlalchemy(self, conf): """Setup SQLAlchemy database engine. The most common reason for modifying this method is to add @@ -836,59 +779,65 @@ """ from sqlalchemy import engine_from_config - balanced_master = config.get('sqlalchemy.master.url') + balanced_master = conf.get('sqlalchemy.master.url') if not balanced_master: - engine = engine_from_config(config, 'sqlalchemy.') + engine = engine_from_config(conf, 'sqlalchemy.') else: - engine = engine_from_config(config, 'sqlalchemy.master.') - config['balanced_engines'] = {'master':engine, - 'slaves':{}, - 'all':{'master':engine}} - - all_engines = config['balanced_engines']['all'] - slaves = config['balanced_engines']['slaves'] - for entry in config.keys(): + engine = engine_from_config(conf, 'sqlalchemy.master.') + conf['balanced_engines'] = {'master':engine, + 'slaves':{}, + 'all':{'master':engine}} + + all_engines = conf['balanced_engines']['all'] + slaves = conf['balanced_engines']['slaves'] + for entry in conf.keys(): if entry.startswith('sqlalchemy.slaves.'): slave_path = entry.split('.') slave_name = slave_path[2] if slave_name == 'master': raise TGConfigError('A slave node cannot be named master') slave_config = '.'.join(slave_path[:3]) - all_engines[slave_name] = slaves[slave_name] = engine_from_config(config, slave_config+'.') + all_engines[slave_name] = slaves[slave_name] = engine_from_config(conf, slave_config+'.') - if not config['balanced_engines']['slaves']: + if not conf['balanced_engines']['slaves']: raise TGConfigError('When running in balanced mode your must specify at least a slave node') # Pass the engine to initmodel, to be able to introspect tables - config['tg.app_globals'].sa_engine = engine + conf['tg.app_globals'].sa_engine = engine try: - package_models = self.package.model + package_models = conf['package'].model except AttributeError: package_models = None - model = getattr(self, 'model', package_models) + model = conf.get('model', package_models) if model is None: raise TGConfigError('SQLAlchemy enabled, but no models provided') - model.init_model(engine) + sqla_session = model.init_model(engine) + if sqla_session is not None: + # If init_model returns a specific session, keep it around + # as the SQLAlchemy Session. + conf['SQLASession'] = sqla_session - if not hasattr(self, 'DBSession'): - # If the user hasn't specified a scoped_session, assume + if 'DBSession' not in conf: + # If the user hasn't specified a default session, assume # he/she uses the default DBSession in model - self.DBSession = model.DBSession + conf['DBSession'] = model.DBSession - def setup_auth(self): + def _setup_auth(self, conf): """ Override this method to define how you would like the authentication options to be setup for your application. """ if hasattr(self, 'setup_sa_auth_backend'): warnings.warn("setup_sa_auth_backend is deprecated, please override" - "AppConfig.setup_auth instead", DeprecationWarning) - self.setup_sa_auth_backend() - elif self.auth_backend in ("ming", "sqlalchemy"): - if 'cookie_secret' not in self.sa_auth and 'session.secret' not in config: + "AppConfig._setup_auth instead", DeprecationWarning) + return self.setup_sa_auth_backend() + + if conf['auth_backend'] in ("ming", "sqlalchemy"): + sa_auth_conf = conf['sa_auth'] + if 'cookie_secret' not in sa_auth_conf and 'session.secret' not in conf: raise TGConfigError("You must provide a value for authentication cookies secret. " "Make sure that you have one of those options in app_cfg.py: " "'sa_auth.cookie_secret' or 'session.secret'") @@ -896,28 +845,31 @@ # The developer must have defined a 'sa_auth' section, because # values such as the User, Group or Permission classes must be # explicitly defined. - self.sa_auth.setdefault('form_plugin', None) - self.sa_auth.setdefault( + sa_auth_conf.setdefault('form_plugin', None) + sa_auth_conf.setdefault( 'cookie_secret', - config.get('session.secret', config.get('beaker.session.secret')) + conf.get('session.secret', conf.get('beaker.session.secret')) ) - def _setup_controller_wrappers(self): + def _setup_controller_wrappers(self, conf): # This trashes away the current config['controller_caller'] # so that the call is idempotent. base_controller_caller = call_controller controller_caller = base_controller_caller - for wrapper in self.get('controller_wrappers', []): + for wrapper in conf.get('controller_wrappers', []): try: controller_caller = wrapper(controller_caller) except TypeError: - controller_caller = _DeprecatedControllerWrapper(wrapper, self, controller_caller) + controller_caller = _DeprecatedControllerWrapper(wrapper, conf, controller_caller) - config['controller_caller'] = controller_caller + conf['controller_caller'] = controller_caller - def _setup_renderers(self): - for renderer in self.renderers[:]: + def _setup_renderers(self, conf): + renderers = conf['renderers'] + rendering_engines = conf['rendering_engines'] + + for renderer in renderers[:]: setup = getattr(self, 'setup_%s_renderer'%renderer, None) if setup is not None: # Backward compatible old-way of configuring rendering engines @@ -929,17 +881,18 @@ success = setup() if success is False: log.error('Failed to initialize %s template engine, removing it...' % renderer) - self.renderers.remove(renderer) - elif renderer in self.rendering_engines: - rendering_engine = self.rendering_engines[renderer] - engines = rendering_engine.create(config, config['tg.app_globals']) + renderers.remove(renderer) + elif renderer in rendering_engines: + rendering_engine = rendering_engines[renderer] + engines = rendering_engine.create(conf, conf['tg.app_globals']) if engines is None: log.error('Failed to initialize %s template engine, removing it...' % renderer) - self.renderers.remove(renderer) + renderers.remove(renderer) else: - self.render_functions.update(engines) + conf['render_functions'].update(engines) else: - raise TGConfigError('This configuration object does not support the %s renderer' % renderer) + raise TGConfigError('This configuration object does ' + 'not support the %s renderer' % renderer) milestones.renderers_ready.reach() @@ -962,11 +915,10 @@ tg.hooks.notify('initialized_config', args=(self, app_config)) tg.hooks.notify('startup', trap_exceptions=True) - self.setup_routes() - self.setup_helpers_and_globals() - self.setup_auth() - self._setup_renderers() - self.setup_persistence() + self._setup_helpers_and_globals(app_config) + self._setup_auth(app_config) + self._setup_renderers(app_config) + self._setup_persistence(app_config) # Trigger milestone here so that it gets triggered even when # websetup (setup-app command) is performed. @@ -976,52 +928,46 @@ return load_environment - def add_error_middleware(self, global_conf, app): + def _add_error_middleware(self, app_config, app): """Add middleware which handles errors and exceptions.""" from tg.error import ErrorReporter - app = ErrorReporter(app, global_conf, **config['tg.errorware']) + app = ErrorReporter(app, app_config, **app_config['tg.errorware']) - if self.status_code_redirect is True: + if app_config['status_code_redirect'] is True: warnings.warn("Support for StatusCodeRedirect is deprecated and " "will be removed in next major release", DeprecationWarning) - if self.handle_error_page: + if app_config['handle_error_page']: from tg.support.middlewares import StatusCodeRedirect # Display error documents for self.handle_status_codes status codes (and # 500 when debug is disabled) - if asbool(config['debug']): - app = StatusCodeRedirect(app, self.handle_status_codes) - else: - app = StatusCodeRedirect(app, self.handle_status_codes + [500]) - + handled_status_codes = app_config['handle_status_codes'] + if not asbool(app_config['debug']): + handled_status_codes += [500] + app = StatusCodeRedirect(app, handled_status_codes) return app - def add_slowreqs_middleware(self, global_conf, app): + def _add_slowreqs_middleware(self, app_config, app): from tg.error import SlowReqsReporter - return SlowReqsReporter(app, global_conf, **config['tg.slowreqs']) + return SlowReqsReporter(app, app_config, **app_config['tg.slowreqs']) - def add_debugger_middleware(self, global_conf, app): + def _add_debugger_middleware(self, app_config, app): from tg.error import ErrorHandler - return ErrorHandler(app, global_conf) + return ErrorHandler(app, app_config) - def add_auth_middleware(self, app, skip_authentication): + def _add_auth_middleware(self, app_config, app): """ Configure authentication and authorization. - - :param app: The TG2 application. - :param skip_authentication: Should authentication be skipped if - explicitly requested? (used by repoze.who-testutil) - :type skip_authentication: bool - """ # Start with the current configured authentication options. # Depending on the auth backend a new auth_args dictionary # can replace this one later on. - auth_args = copy(self.sa_auth) + skip_authentication = asbool(app_config.get('skip_authentication', False)) + auth_args = copy(app_config['sa_auth']) # Configuring auth logging: - if 'log_stream' not in self.sa_auth: + if 'log_stream' not in auth_args: auth_args['log_stream'] = logging.getLogger('auth') # Removing keywords not used by repoze.who: @@ -1087,162 +1033,104 @@ return app - def add_core_middleware(self, app): - """Add support for routes dispatch, sessions, and caching middlewares + def _add_core_middleware(self, conf, app): + """Add support for sessions, and caching middlewares Those are all deprecated middlewares and will be removed in future TurboGears versions as they have been replaced by other tools. """ - if self.enable_routes: - warnings.warn("Internal routes support will be deprecated soon, please " - "consider using tgext.routes instead", DeprecationWarning) - from routes.middleware import RoutesMiddleware - app = RoutesMiddleware(app, config['routes.map']) - - if getattr(self, 'use_session_middleware', False): + if conf.get('use_session_middleware', False): warnings.warn('SessionMiddleware is deprecated and will be removed soon, ' 'please consider using SessionApplicationWrapper instead.', DeprecationWarning) from tg.support.middlewares import SessionMiddleware, BeakerSessionMiddleware if BeakerSessionMiddleware is object: # pragma: no cover raise ImportError('Beaker not installed') - app = SessionMiddleware(app, config) + app = SessionMiddleware(app, conf) - if getattr(self, 'use_cache_middleware', False): + if conf.get('use_cache_middleware', False): warnings.warn('CacheMiddleware is deprecated and will be removed soon, ' 'please consider using CacheApplicationWrapper instead.', DeprecationWarning) from tg.support.middlewares import CacheMiddleware, BeakerCacheMiddleware if BeakerCacheMiddleware is object: # pragma: no cover raise ImportError('Beaker not installed') - app = CacheMiddleware(app, config) + app = CacheMiddleware(app, conf) return app - def add_tosca_middleware(self, app): - """Configure the ToscaWidgets middleware. - - If you would like to override the way the TW middleware works, you might do something like:: - - from tg.configuration import AppConfig - from tw.api import make_middleware as tw_middleware - - class MyAppConfig(AppConfig): - - def add_tosca2_middleware(self, app): - - app = tw_middleware(app, { - 'toscawidgets.framework.default_view': self.default_renderer, - 'toscawidgets.framework.translator': ugettext, - 'toscawidgets.middleware.inject_resources': False, - }) - return app - - base_config = MyAppConfig() - - - - The above example would disable resource injection. - - There is more information about the settings you can change - in the ToscaWidgets `middleware. ` - - - """ - + def _add_tosca_middleware(self, conf, app): + """Configure the ToscaWidgets middleware""" import tw from tw.api import make_middleware as tw_middleware from tg.i18n import ugettext - twconfig = {'toscawidgets.framework.default_view': self.default_renderer, + twconfig = {'toscawidgets.framework.default_view': conf['default_renderer'], 'toscawidgets.framework.translator': ugettext, 'toscawidgets.middleware.inject_resources': True, } - for k,v in config.items(): + for k,v in conf.items(): if k.startswith('toscawidgets.framework.') or k.startswith('toscawidgets.middleware.'): twconfig[k] = v - if 'toscawidgets.framework.resource_variant' in config: + resource_variant = conf.get('toscawidgets.framework.resource_variant') + if resource_variant is not None: import tw.api - tw.api.resources.registry.ACTIVE_VARIANT = config['toscawidgets.framework.resource_variant'] - #remove it from the middleware madness + tw.api.resources.registry.ACTIVE_VARIANT = resource_variant + # remove it from the middleware madness del twconfig['toscawidgets.framework.resource_variant'] app = tw_middleware(app, twconfig) - if self.default_renderer in ('genshi','mako'): - tw.framework.default_view = self.default_renderer + if conf['default_renderer'] in ('genshi','mako'): + tw.framework.default_view = conf['default_renderer'] return app - def add_tosca2_middleware(self, app): - """Configure the ToscaWidgets2 middleware. - - If you would like to override the way the TW2 middleware works, - you might do change your app_cfg.py to add something like:: - - from tg.configuration import AppConfig - from tw2.core.middleware import TwMiddleware - - class MyAppConfig(AppConfig): - - def add_tosca2_middleware(self, app): - - app = TwMiddleware(app, - default_engine=self.default_renderer, - translator=ugettext, - auto_reload_templates = False - ) - - return app - base_config = MyAppConfig() - - - - The above example would always set the template auto reloading off. (This is normally an - option that is set within your application's ini file.) - """ + def _add_tosca2_middleware(self, conf, app): + """Configure the ToscaWidgets2 middleware""" from tw2.core.middleware import Config, TwMiddleware from tg.i18n import ugettext, get_lang - shared_engines = list(set(self.renderers) & set(Config.preferred_rendering_engines)) + available_renderers = set(conf['renderers']) + shared_engines = list(available_renderers & set(Config.preferred_rendering_engines)) if not shared_engines: raise TGConfigError('None of the configured rendering engines is supported' 'by ToscaWidgets2, unable to configure ToscaWidgets.') - if self.default_renderer in shared_engines: - tw2_engines = [self.default_renderer] + shared_engines - tw2_default_engine = self.default_renderer + default_renderer = conf['default_renderer'] + if default_renderer in shared_engines: + tw2_engines = [default_renderer] + shared_engines + tw2_default_engine = default_renderer else: # If preferred rendering engine is not available in TW2, fallback to another one - # This happens for Kajiki which is not supported by recent TW2 versions. tw2_engines = shared_engines tw2_default_engine = shared_engines[0] - default_tw2_config = dict( default_engine=tw2_default_engine, - preferred_rendering_engines=tw2_engines, - translator=ugettext, - get_lang=lambda: get_lang(all=False), - auto_reload_templates=config['auto_reload_templates'], - controller_prefix='/tw2/controllers/', - res_prefix='/tw2/resources/', - debug=config['debug'], - rendering_extension_lookup={ - 'mako': ['mak', 'mako'], - 'genshi': ['genshi', 'html'], - 'jinja':['jinja', 'jinja2'], - 'kajiki':['kajiki', 'xhtml', 'xml'] - }) + default_tw2_config = dict(default_engine=tw2_default_engine, + preferred_rendering_engines=tw2_engines, + translator=ugettext, + get_lang=lambda: get_lang(all=False), + auto_reload_templates=conf['auto_reload_templates'], + controller_prefix='/tw2/controllers/', + res_prefix='/tw2/resources/', + debug=conf['debug'], + rendering_extension_lookup={ + 'mako': ['mak', 'mako'], + 'genshi': ['genshi', 'html'], + 'jinja': ['jinja', 'jinja2'], + 'kajiki': ['kajiki', 'xhtml', 'xml'] + }) - default_tw2_config.update(self.custom_tw2_config) + default_tw2_config.update(conf.get('custom_tw2_config', {})) app = TwMiddleware(app, **default_tw2_config) return app - def add_static_file_middleware(self, app): - app = StaticsMiddleware(app, config['paths']['static_files']) + def _add_static_file_middleware(self, conf, app): + app = StaticsMiddleware(app, conf['paths']['static_files']) return app - def add_tm_middleware(self, app): + def _add_tm_middleware(self, conf, app): """Set up the transaction management middleware. To abort a transaction inside a TG2 app:: @@ -1254,33 +1142,40 @@ behavior can be overridden by overriding base_config['tm.commit_veto']. """ + warnings.warn("transaction manager middleware has been replaced by " + "TransactionApplicationWrapper", DeprecationWarning, stacklevel=2) from tg.support.transaction_manager import TGTransactionManager - try: - # TODO: remove self.commit_veto option in future release - # backward compatibility with "commit_veto" option - config['tm.commit_veto'] = self.commit_veto + # TODO: remove self.commit_veto option in future release + # backward compatibility with "commit_veto" option + if 'commit_veto' in conf: + conf['tm.commit_veto'] = conf['commit_veto'] warnings.warn("commit_veto option has been replaced by tm.commit_veto", DeprecationWarning, stacklevel=2) - except AttributeError: - pass - return TGTransactionManager(app, config) + return TGTransactionManager(app, conf) - def add_ming_middleware(self, app): + def _add_ming_middleware(self, conf, app): """Set up the ming middleware for the unit of work""" from tg.support.middlewares import MingSessionRemoverMiddleware from ming.odm import ThreadLocalODMSession return MingSessionRemoverMiddleware(ThreadLocalODMSession, app) - def add_sqlalchemy_middleware(self, app): + def _add_sqlalchemy_middleware(self, conf, app): """Set up middleware that cleans up the sqlalchemy session. The default behavior of TG 2 is to clean up the session on every request. Only override this method if you know what you are doing! """ - return DBSessionRemoverMiddleware(self.DBSession, app) + dbsession = conf.get('SQLASession') + if dbsession is None: + dbsession = conf['DBSession'] + return DBSessionRemoverMiddleware(dbsession, app) + + def _add_seekable_body_middleware(self, conf, app): + """Make the request body seekable, so it can be read multiple times.""" + return SeekableRequestBodyMiddleware(app) def setup_tg_wsgi_app(self, load_environment=None): """Create a base TG app, with all the standard middleware. @@ -1326,80 +1221,81 @@ global_conf = {} # Configure the Application environment - app_config = None if load_environment: app_config = load_environment(global_conf, app_conf) + else: + app_config = tg.config._current_obj() - # trigger the environment_loaded milestone again, so that - # when load_environment is not provided the attached actions gets performed anyway. - milestones.environment_loaded.reach() + # In case load_environment was not performed we manually trigger all + # the milestones to ensure that events related to configuration milestones + # are performed in any case. + milestones.config_ready.reach() + milestones.renderers_ready.reach() + milestones.environment_loaded.reach() # Apply controller wrappers to controller caller - self._setup_controller_wrappers() + self._setup_controller_wrappers(app_config) app = TGApp(app_config) - tg.hooks.notify('configure_new_app', args=(app,), context_config=config) + tg.hooks.notify('configure_new_app', args=(app,)) if wrap_app: app = wrap_app(app) - app = tg.hooks.notify_with_value('before_config', app, context_config=config) + app = tg.hooks.notify_with_value('before_config', app) - app = self.add_core_middleware(app) + app = self._add_core_middleware(app_config, app) - if self.auth_backend: - # Skipping authentication if explicitly requested. - # Used by repoze.who-testutil: - skip_authentication = app_conf.get('skip_authentication', False) - app = self.add_auth_middleware(app, skip_authentication) - - if getattr(self, 'use_transaction_manager', False): - app = self.add_tm_middleware(app) + if app_config.get('auth_backend', False): + app = self._add_auth_middleware(app_config, app) + if app_config.get('use_transaction_manager', False): + app = self._add_tm_middleware(app_config, app) # TODO: Middlewares before this point should be converted to App Wrappers. # They provide some basic TG features like AUTH, Caching and transactions # which should be app wrappers to make possible to add wrappers in the # stack before or after them. - if self.use_toscawidgets: - app = self.add_tosca_middleware(app) + if app_config.get('use_toscawidgets', False): + app = self._add_tosca_middleware(app_config, app) - if self.use_toscawidgets2: - app = self.add_tosca2_middleware(app) + if app_config.get('use_toscawidgets2', False): + app = self._add_tosca2_middleware(app_config, app) - # from here on the response is a generator + # from here on, due to TW2, the response is a generator # so any middleware that relies on the response to be # a string needs to be applied before this point. - if self.use_sqlalchemy: - app = self.add_sqlalchemy_middleware(app) - if self.use_ming: - app = self.add_ming_middleware(app) + if app_config.get('use_sqlalchemy', False): + app = self._add_sqlalchemy_middleware(app_config, app) + + if app_config.get('use_ming', False): + app = self._add_ming_middleware(app_config, app) - if config.get('make_body_seekable'): - app = SeekableRequestBodyMiddleware(app) + if app_config.get('make_body_seekable', False): + app = self._add_seekable_body_middleware(app_config, app) if asbool(full_stack): # This should never be true for internal nested apps - app = self.add_slowreqs_middleware(global_conf, app) - app = self.add_error_middleware(global_conf, app) + app = self._add_slowreqs_middleware(app_config, app) + app = self._add_error_middleware(app_config, app) # Establish the registry for this application - app = RegistryManager(app, streaming=config.get('registry_streaming', True), - preserve_exceptions=asbool(global_conf.get('debug'))) + app = RegistryManager(app, streaming=asbool(app_config.get('registry_streaming', True)), + preserve_exceptions=asbool(app_config.get('debug'))) # Place the debuggers after the registry so that we # can preserve context in case of exceptions - app = self.add_debugger_middleware(global_conf, app) + app = self._add_debugger_middleware(app_config, app) # Static files (if running in production, and Apache or another # web server is serving static files) - if config['serve_static']: - app = self.add_static_file_middleware(app) + if app_config.get('serve_static', False): + app = self._add_static_file_middleware(app_config, app) - app = tg.hooks.notify_with_value('after_config', app, context_config=config) + app = tg.hooks.notify_with_value('after_config', app) return app diff -Nru turbogears2-2.3.7/tg/configuration/auth/setup.py turbogears2-2.3.12/tg/configuration/auth/setup.py --- turbogears2-2.3.7/tg/configuration/auth/setup.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tg/configuration/auth/setup.py 2018-04-06 11:31:55.000000000 +0000 @@ -119,12 +119,18 @@ # If no identifiers are provided in repoze setup arguments # then create a default one using AuthTktCookiePlugin. - if 'identifiers' not in who_args: + identifiers = who_args.setdefault('identifiers', [('default', None)]) + try: + default_identifier_index = identifiers.index(('default', None)) + except ValueError: + pass + else: + # default identifier included. from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin cookie = AuthTktCookiePlugin(cookie_secret, cookie_name, timeout=cookie_timeout, reissue_time=cookie_reissue_time) - who_args['identifiers'] = [('cookie', cookie)] + who_args['identifiers'][default_identifier_index] = ('cookie', cookie) who_args['authenticators'].insert(0, ('cookie', cookie)) # If no form plugin is provided then create a default diff -Nru turbogears2-2.3.7/tg/configuration/hooks.py turbogears2-2.3.12/tg/configuration/hooks.py --- turbogears2-2.3.7/tg/configuration/hooks.py 2015-04-28 20:01:51.000000000 +0000 +++ turbogears2-2.3.12/tg/configuration/hooks.py 2018-04-06 11:31:55.000000000 +0000 @@ -157,7 +157,7 @@ if self.hook_name == 'controller_wrapper': warnings.warn('controller wrappers should be registered on ' 'AppConfig using AppConfig.register_controller_wrapper', - DeprecationWarning) + DeprecationWarning, stacklevel=3) config = tg_config._current_obj() config['controller_wrappers'].append(self.func) @@ -179,7 +179,7 @@ if self.hook_name == 'controller_wrapper': warnings.warn('controller wrappers should be registered on ' 'AppConfig using AppConfig.register_controller_wrapper', - DeprecationWarning) + DeprecationWarning, stacklevel=3) deco = Decoration.get_decoration(self.controller) deco._register_controller_wrapper(self.func) diff -Nru turbogears2-2.3.7/tg/configuration/utils.py turbogears2-2.3.12/tg/configuration/utils.py --- turbogears2-2.3.7/tg/configuration/utils.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/configuration/utils.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,3 +1,8 @@ +import inspect +from collections import deque + +import itertools + from .milestones import config_ready @@ -113,3 +118,119 @@ def _load_config(self): from tg.configuration import config self.configure(**coerce_config(config, self.CONFIG_NAMESPACE, self.CONFIG_OPTIONS)) + + +class DependenciesList(object): + """Manages a list of entries which might depend one from the other. + + This powers :meth:`.AppConfig.register_wrapper` and other features in + TurboGears2, making possible to register the wrappers right after + other wrappers or at the end of the wrappers chain. + + .. note:: This is highly inefficient as it is only meant to run at configuration time, + a new implementation will probably be provided based on heapq in the future. + """ + #: Those are the heads of the dependencies tree + #: - ``False`` means before everything else + #: - ``None`` means in the middle. + #: - ``True`` means after everything else. + DEPENDENCY_HEADS = (False, None, True) + + def __init__(self, *entries): + self._dependencies = {} + self._ordered_elements = [] + self._inserted_keys = [] + + for entry in entries: + self.add(entry) + + def add(self, entry, key=None, after=None): + """Adds an entry to the dependencies list. + + :param entry: Entry that must be added to the list. + :param str|type|None key: An identifier of the object being inserted. + This is used by later insertions as ``after`` argument + to specify after which object the new one should be inserted. + :param str|type|None|False|True after: After which element this one should be inserted. + This is the ``key`` of a previously inserted item. + In case no item with ``key`` has been inserted, the + entry will be inserted in normal order of insertion. + Also accepts one of + :attr:`.DependenciesList.DEPENDENCY_HEADS` as key + to add entries at begin or end of the list. + """ + if key is None: + if inspect.isclass(entry): + key = entry.__name__ + else: + # Inserting an object without a key would lead to unexpected ordering. + # we cannot use the object class as the key would not be unique across + # different instances. + raise ValueError('Inserting instances without a key is not allowed') + + if after not in self.DEPENDENCY_HEADS and not isinstance(after, str): + if inspect.isclass(after): + after = after.__name__ + else: + raise ValueError('after parameter must be a string, a class or a special value') + + self._inserted_keys.append(key) + self._dependencies.setdefault(after, []).append((key, entry)) + self._resolve_ordering() + + def __repr__(self): + return '' % [x[0] for x in self._ordered_elements] + + def __iter__(self): + return iter(self._ordered_elements) + + def values(self): + """Returns all the inserted values without their key as a generator""" + return (x[1]for x in self._ordered_elements) + + def replace(self, key, newvalue): + """Replaces entry associated to key with a new one. + + :param newvalue: Entry that must replace the previous value. + :param str|type key: An identifier of the object being inserted. + """ + if not isinstance(key, str): + if inspect.isclass(key): + key = key.__name__ + else: + raise ValueError('key parameter must be a string or a class') + + for entries in self._dependencies.values(): + for idx, value in enumerate(entries): + entry_key, entry_value = value + if entry_key == key: + entries[idx] = (entry_key, newvalue) + + self._resolve_ordering() + + def _resolve_ordering(self): + ordered_elements = [] + + existing_dependencies = set(self._inserted_keys) | set(self.DEPENDENCY_HEADS) + dependencies_without_heads = set(self._dependencies.keys()) - set(self.DEPENDENCY_HEADS) + + # All entries that depend on a missing entry are converted + # to depend from None so they end up being in the middle. + dependencies = {} + for dependency in itertools.chain(self.DEPENDENCY_HEADS, dependencies_without_heads): + entries = self._dependencies.get(dependency, []) + if dependency not in existing_dependencies: + dependency = None + dependencies.setdefault(dependency, []).extend(entries) + + # Resolve the dependencies and generate the ordered elements. + visit_queue = deque((head, head) for head in self.DEPENDENCY_HEADS) + while visit_queue: + current_key, current_obj = visit_queue.popleft() + if current_key not in self.DEPENDENCY_HEADS: + ordered_elements.append((current_key, current_obj)) + + element_dependencies = dependencies.pop(current_key, []) + visit_queue.extendleft(reversed(element_dependencies)) + + self._ordered_elements = ordered_elements diff -Nru turbogears2-2.3.7/tg/controllers/decoratedcontroller.py turbogears2-2.3.12/tg/controllers/decoratedcontroller.py --- turbogears2-2.3.7/tg/controllers/decoratedcontroller.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/controllers/decoratedcontroller.py 2018-04-06 11:31:55.000000000 +0000 @@ -11,8 +11,7 @@ from tg.controllers.util import abort from tg.predicates import NotAuthorizedError, not_anonymous -from crank.util import (get_params_with_argspec, - remove_argspec_params_from_params) +from crank.util import get_params_with_argspec, flatten_arguments from tg.flash import flash from tg.jsonify import JsonEncodeError @@ -51,7 +50,7 @@ if method and inspect.ismethod(method) and hasattr(method, 'decoration'): return method.decoration.exposed - def _call(self, controller, params, remainder=None, context=None): + def _call(self, action, params, remainder=None, context=None): """Run the controller with the given parameters. _call is called by _perform_call in CoreDispatcher. @@ -95,39 +94,50 @@ remainder = tuple() hooks.notify('before_validate', args=(remainder, params), - controller=controller, context_config=context_config) + controller=action, context_config=context_config) - try: - validate_params = get_params_with_argspec(controller, params, remainder) - context.request.args_params = validate_params # Update args_params with positional args - - # Validate user input - params = self._perform_validate(controller, validate_params, context) - context.request.validation.values = params + validate_params = get_params_with_argspec(action, params, remainder) + context.request.args_params = validate_params # Update args_params with positional args - params, remainder = remove_argspec_params_from_params(controller, params, remainder) - bound_controller_callable = controller + try: + params = self._perform_validate(action, validate_params, context) except validation_errors as inv: - instance, controller = self._process_validation_errors(controller, - remainder, params, - inv, context=context) - bound_controller_callable = partial(controller, instance) + instance, error_handler, chain_validation = self._process_validation_errors( + action, remainder, params, inv, context=context + ) + while chain_validation: + # The validation asked for chained validation, + # go on and validate the error_handler too. + try: + params = self._perform_validate(error_handler, validate_params, context) + except validation_errors as inv: + instance, error_handler, chain_validation = self._process_validation_errors( + error_handler, remainder, params, inv, context=context + ) + else: + chain_validation = False + action = error_handler + bound_controller_callable = partial(error_handler, instance) + else: + bound_controller_callable = action + context.request.validation.values = params + remainder, params = flatten_arguments(action, params, remainder) hooks.notify('before_call', args=(remainder, params), - controller=controller, context_config=context_config) + controller=action, context_config=context_config) # call controller method with applied wrappers - controller_caller = controller.decoration.controller_caller + controller_caller = action.decoration.controller_caller output = controller_caller(context_config, bound_controller_callable, remainder, params) # Render template hooks.notify('before_render', args=(remainder, params, output), - controller=controller, context_config=context_config) + controller=action, context_config=context_config) - response = self._render_response(context, controller, output) + response = self._render_response(context, action, output) hooks.notify('after_render', args=(response,), - controller=controller, context_config=context_config) + controller=action, context_config=context_config) return response['response'] @@ -156,7 +166,7 @@ if context is None: # pragma: no cover warnings.warn("Calling DecoratedController._perform_validate without a Context is now deprecated." " Please provide the context argument when calling it.", - DeprecationWarning) + DeprecationWarning, stacklevel=2) context = tg.request.environ['tg.locals'] validations = controller.decoration.validations @@ -193,29 +203,29 @@ req = tgl.request resp = tgl.response - (content_type, engine_name, template_name, exclude_names, render_params - ) = controller.decoration.lookup_template_engine(tgl) + (engine_content_type, engine_name, template_name, + exclude_names, render_params) = controller.decoration.lookup_template_engine(tgl) - result = dict(response=response, content_type=content_type, + result = dict(response=response, content_type=engine_content_type, engine_name=engine_name, template_name=template_name) - if content_type is not None: - resp.headers['Content-Type'] = content_type + if resp.content_type is None and engine_content_type is not None: + # User didn't set a specific content type during controller + # and template engine has a suggested one. Use template engine one. + resp.headers['Content-Type'] = engine_content_type + + content_type = resp.headers['Content-Type'] + if 'charset' not in content_type and ( + content_type.startswith('text') or content_type in ('application/xhtml+xml', + 'application/xml', + 'application/json') + ): + resp.content_type = content_type + '; charset=utf-8' # if it's a string return that string and skip all the stuff if not isinstance(response, dict): - if engine_name == 'json' and isinstance(response, list): - raise JsonEncodeError( - 'You may not expose with JSON a list return value because' - ' it leaves your application open to CSRF attacks.') return result - # Save these objects as locals from the SOP to avoid expensive lookups - tmpl_context = tgl.tmpl_context - - # If there is an identity, push it to the Pylons template context - tmpl_context.identity = req.environ.get('repoze.who.identity') - # Setup the template namespace, removing anything that the user # has marked to be excluded. namespace = response @@ -287,10 +297,12 @@ # Get the error handler associated to the current validation status. error_handler = validation_status.error_handler + chain_validation = validation_status.chain_validation if error_handler is None: error_handler = default_im_func(controller) + chain_validation = False - return im_self(controller), error_handler + return im_self(controller), error_handler, chain_validation @classmethod def _handle_validation_errors(cls, controller, remainder, params, exception, tgl=None): @@ -307,8 +319,8 @@ # compatibility with old code that didn't pass request locals explicitly tgl = tg.request.environ['tg.locals'] - obj, error_handler = cls._process_validation_errors(controller, remainder, params, - exception, tgl) + obj, error_handler,_ = cls._process_validation_errors(controller, remainder, params, + exception, tgl) return error_handler, error_handler(obj, *remainder, **dict(params)) def _check_security(self): diff -Nru turbogears2-2.3.7/tg/controllers/dispatcher.py turbogears2-2.3.12/tg/controllers/dispatcher.py --- turbogears2-2.3.7/tg/controllers/dispatcher.py 2015-04-28 20:01:51.000000000 +0000 +++ turbogears2-2.3.12/tg/controllers/dispatcher.py 2018-04-06 11:31:55.000000000 +0000 @@ -15,7 +15,7 @@ class which provides the ordinary TurboGears mechanism. """ -import tg, sys +import tg from webob.exc import HTTPException from tg._compat import unicode_text from tg.decorators import cached_property @@ -23,6 +23,7 @@ from tg.request_local import WebObResponse import mimetypes as default_mimetypes import weakref +from ..wsgiapp import TGApp def dispatched_controller(): @@ -37,7 +38,7 @@ _use_lax_params = True _use_index_fallback = False - def _get_dispatchable(self, thread_locals, url_path): + def _get_dispatchable(self, context, url_path): """ Returns a tuple (controller, remainder, params) @@ -45,18 +46,16 @@ url url as string """ - req = thread_locals.request - conf = thread_locals.config + req = context.request + conf = context.config enable_request_extensions = not conf.get('disable_request_extensions', False) dispatch_path_translator = conf.get('dispatch_path_translator', True) - params = req.args_params - state = DispatchState(weakref.proxy(req), self, params, url_path.split('/'), + state = DispatchState(weakref.proxy(req), self, req.args_params, url_path.split('/'), conf.get('ignore_parameters', []), strip_extension=enable_request_extensions, path_translator=dispatch_path_translator) - url_path = state.path # Get back url_path as crank performs some cleaning if enable_request_extensions: try: @@ -71,18 +70,18 @@ req._fast_setattr('_response_type', mime_type) req._fast_setattr('_response_ext', ext) - state = state.controller._dispatch(state, url_path) + state = state.resolve() - #save the controller state for possible use within the controller methods + # Save the dispatch state for possible use within the controller methods req._fast_setattr('_controller_state', state) if conf.get('enable_routing_args', False): - state.routing_args.update(params) - if hasattr(state.dispatcher, '_setup_wsgiorg_routing_args'): - state.dispatcher._setup_wsgiorg_routing_args(url_path, state.remainder, - state.routing_args) + state.routing_args.update(state.params) + if hasattr(state.root_dispatcher, '_setup_wsgiorg_routing_args'): + state.root_dispatcher._setup_wsgiorg_routing_args(state.path, state.remainder, + state.routing_args) - return state, params + return state def _enter_controller(self, state, remainder): if hasattr(state.controller, '_visit'): @@ -96,31 +95,23 @@ execution. """ py_request = context.request - py_config = context.config - state, params = self._get_dispatchable(context, py_request.quoted_path_info) - func, controller, remainder = state.method, state.controller, state.remainder + state = self._get_dispatchable(context, py_request.quoted_path_info) + controller, action = state.controller, state.action + params, remainder = state.params, state.remainder if hasattr(controller, '_before'): controller._before(*remainder, **params) self._setup_wsgi_script_name(state.path, remainder, params) - r = self._call(func, params, remainder=remainder, context=context) + r = self._call(action, params, remainder=remainder, context=context) if hasattr(controller, '_after'): controller._after(*remainder, **params) return r - def routes_placeholder(self, url='/', start_response=None, **kwargs): #pragma: no cover - """Routes placeholder. - - This function does not do anything. It is a placeholder that allows - Routes to accept this controller as a target for its routing. - """ - pass - def __call__(self, environ, context): py_response = context.response @@ -129,23 +120,35 @@ except HTTPException as httpe: response = httpe - if isinstance(response, bytes): + if response is tg.response or response is py_response: + # Controller returned the response itself, so we need to do nothing. + return response + + if response is None: + # No content + py_response.body = b'' + if py_response.status_int == 200: + # Ensure that for missing content we return 'No Content', instead of 200 OK + py_response.content_type = None + py_response.status_int = 204 + elif isinstance(response, bytes): py_response.body = response elif isinstance(response, unicode_text): if not py_response.charset: py_response.charset = 'utf-8' py_response.text = response elif isinstance(response, WebObResponse): - py_response.content_length = response.content_length + # Copy eventual headers from tg.response for name, value in py_response.headers.items(): header_name = name.lower() + if header_name in ('content-type', 'content-length'): + # Do not overwrite content related headers in returned response + continue if header_name == 'set-cookie': response.headers.add(name, value) else: response.headers.setdefault(name, value) py_response = context.response = response - elif response is None: - pass else: py_response.app_iter = response @@ -177,5 +180,6 @@ if 'tg.root_controller' in tg.config: root_controller = tg.config['tg.root_controller'] else: - root_controller = sys.modules[tg.config['application_root_module']].RootController + root_controller = TGApp.lookup_controller(tg.config, 'root') + return find_url(root_controller, self, [('/', root_controller)]) diff -Nru turbogears2-2.3.7/tg/controllers/restcontroller.py turbogears2-2.3.12/tg/controllers/restcontroller.py --- turbogears2-2.3.7/tg/controllers/restcontroller.py 2014-10-03 19:43:51.000000000 +0000 +++ turbogears2-2.3.12/tg/controllers/restcontroller.py 2018-04-06 11:31:55.000000000 +0000 @@ -5,6 +5,7 @@ """ from crank.restdispatcher import RestDispatcher +from tg.decorators import expose from tg.controllers.dispatcher import CoreDispatcher from tg.controllers.decoratedcontroller import DecoratedController @@ -69,4 +70,16 @@ """ + @expose() + def options(self, *args, **kw): + """The OPTIONS http verb is automatically sent by the browser when doing cross origin requests. + + This default method was added to prevent receiving 404 HTTP Not found error. + It returns None which translates to 204 No Content for HTTP. + You can use the _before hook to set up your CORS headers. + + """ + return None + + __all__ = ['RestController'] diff -Nru turbogears2-2.3.7/tg/controllers/tgcontroller.py turbogears2-2.3.12/tg/controllers/tgcontroller.py --- turbogears2-2.3.7/tg/controllers/tgcontroller.py 2014-10-03 19:43:51.000000000 +0000 +++ turbogears2-2.3.12/tg/controllers/tgcontroller.py 2018-04-06 11:31:55.000000000 +0000 @@ -14,8 +14,7 @@ This controller can be used as a baseclass for anything in the object dispatch tree, but it MUST be used in the Root controller - and any controller which you intend to do object dispatch from - using Routes. + and any controller which you intend to do object dispatch. This controller has a few reserved method names which provide special functionality. diff -Nru turbogears2-2.3.7/tg/controllers/util.py turbogears2-2.3.12/tg/controllers/util.py --- turbogears2-2.3.7/tg/controllers/util.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/controllers/util.py 2018-04-06 11:31:55.000000000 +0000 @@ -71,13 +71,16 @@ return base_url -def url(base_url='/', params=None, qualified=False): +def url(base_url='/', params=None, qualified=False, scheme=None): """Generate an absolute URL that's specific to this application. The URL function takes a string (base_url) and, appends the SCRIPT_NAME and adds parameters for all of the parameters passed into the params dict. + ``scheme`` can be passed in case of a ``qualified`` url to + create an url with the given scheme. + """ if not isinstance(base_url, string_type) and hasattr(base_url, '__iter__'): base_url = '/'.join(base_url) @@ -86,6 +89,8 @@ base_url = _build_url(req.environ, base_url, params) if qualified: base_url = req.host_url + base_url + if scheme is not None: + base_url = scheme + base_url[len(req.scheme):] return base_url @@ -96,15 +101,16 @@ only when you try to display it as a string. """ - def __init__(self, base_url, params=None): + def __init__(self, base_url, params=None, **kwargs): self.base_url = base_url self.params = params + self.kwargs = kwargs self._decoded = None @property def _id(self): if self._decoded == None: - self._decoded = url(self.base_url, params=self.params) + self._decoded = url(self.base_url, params=self.params, **self.kwargs) return self._decoded @property @@ -132,14 +138,14 @@ def startswith(self, *args, **kw): return self._id.startswith(*args, **kw) - def format(self, other): - return self._id.format(other) + def format(self, *args, **kwargs): + return self._id.format(*args, **kwargs) def __json__(self): return str(self) -def lurl(base_url=None, params=None): +def lurl(base_url=None, params=None, **kwargs): """ Like tg.url but is lazily evaluated. @@ -151,10 +157,10 @@ this demands the url resolution to when it is displayed for the first time. """ - return LazyUrl(base_url, params) + return LazyUrl(base_url, params, **kwargs) -def redirect(base_url='/', params=None, redirect_with=HTTPFound, **kwargs): +def redirect(base_url='/', params=None, redirect_with=HTTPFound, scheme=None, **kwargs): """Generate an HTTP redirect. The function raises an exception internally, @@ -175,7 +181,7 @@ params = params.copy() params.update(kwargs) - new_url = url(base_url, params=params) + new_url = url(base_url, params=params, scheme=scheme) raise redirect_with(location=new_url) @@ -246,7 +252,7 @@ def _abortion(*args, **kwargs): if passthrough: tg.request.environ['tg.status_code_redirect'] = False - tg.request.environ['tg.skip_auth_challenge'] = False + tg.request.environ['tg.skip_auth_challenge'] = True raise exc if error_handler is False: @@ -311,8 +317,10 @@ """ req = request._current_obj() - errors = dict(((str(key), str(error)) for key, error in req.validation.errors.items())) - values = req.validation['values'] + errors = dict( + (unicode_text(key), unicode_text(error)) for key, error in req.validation.errors.items() + ) + values = req.validation.values try: return Response(status=412, json_body={'errors': errors, 'values': values}) diff -Nru turbogears2-2.3.7/tg/decorators.py turbogears2-2.3.12/tg/decorators.py --- turbogears2-2.3.7/tg/decorators.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/decorators.py 2018-04-06 11:31:55.000000000 +0000 @@ -104,7 +104,7 @@ @property def requirement(self): # pragma: no cover warnings.warn("Decoration.requirement is deprecated, " - "please use 'requirements' instead", DeprecationWarning) + "please use 'requirements' instead", DeprecationWarning, stacklevel=2) try: return self.requirements[0] except IndexError: @@ -117,7 +117,7 @@ @property def validation(self): warnings.warn("Decoration.validation is deprecated, " - "please use 'validations' instead", DeprecationWarning) + "please use 'validations' instead", DeprecationWarning, stacklevel=2) try: return self.validations[0] except IndexError: @@ -143,13 +143,13 @@ def run_hooks(self, tgl, hook, *l, **kw): warnings.warn("Decoration.run_hooks is deprecated, " - "please use tg.hooks.notify instead", DeprecationWarning) + "please use tg.hooks.notify instead", DeprecationWarning, stacklevel=2) tg.hooks.notify(hook, args=l, kwargs=kw, controller=self.controller, context_config=tgl.config) def register_hook(self, hook_name, func): #pragma: no cover warnings.warn("Decoration.register_hook is deprecated, " - "please use tg.hooks.register instead", DeprecationWarning) + "please use tg.hooks.register instead", DeprecationWarning, stacklevel=2) tg.hooks.register(hook_name, func, controller=self.controller) def register_template_engine(self, @@ -180,7 +180,8 @@ 'skipping engine availability check', template, engine) if engine and available_renderers and engine not in available_renderers: - log.debug('Registering template %s for engine %s not available. Skipping it', template, engine) + log.debug('Registering template %s for engine %s not available. Skipping it', + template, engine) return content_type = content_type or '*/*' @@ -259,7 +260,11 @@ if self.default_engine: content_type = self.default_engine elif self.engines: - if request._response_type and request._response_type in self.engines: + if response.content_type is not None: + # Check for overridden content type from the controller call + accept_types = response.content_type + elif request._response_type and request._response_type in self.engines: + # Check for content type detected by request extensions accept_types = request._response_type else: accept_types = request.headers.get('accept', '*/*') @@ -267,14 +272,6 @@ else: content_type = 'text/html' - # check for overridden content type from the controller call - try: - controller_content_type = response.headers['Content-Type'] - # make sure we handle content_types like 'text/html; charset=utf-8' - content_type = controller_content_type.split(';')[0] - except KeyError: - pass - # check for overridden templates try: cnt_override_mapping = request._override_mapping[self.controller] @@ -283,11 +280,6 @@ (engine, template, exclude_names, render_params ) = self.engines.get(content_type, (None,) * 4) - if 'charset' not in content_type and (content_type.startswith('text') - or content_type in ('application/xhtml+xml', - 'application/xml', 'application/json')): - content_type += '; charset=utf-8' - return content_type, engine, template, exclude_names, render_params def _register_hook(self, hook_name, func): @@ -439,7 +431,7 @@ The last expose decorator example uses the custom_format parameter which takes an arbitrary value (in this case 'special_xml'). - You can then use the`use_custom_format` function within the method + You can then use the :meth:`use_custom_format` function within the method to decide which of the 'custom_format' registered expose decorators to use to render the template. @@ -591,23 +583,19 @@ you can use the ``@validate()`` decorator to register the validators that ought to be called. - :Parameters: - validators - Pass in a dictionary of FormEncode validators. - The keys should match the form field names. - error_handler - Pass in the controller method which shoudl be used - to handle any form errors - form - Pass in a ToscaWidget based form with validators + :param validators: A dictionary of FormEncode/TW2 validators, a :class:`tg.validation.Convert` + or any callable that might throw :class:`tg.validation.TGValidationError`. + :param error_handler: Function or action that should be used to handle the errors. + :param form: A TW2 or ToscaWidgets form to validate ( to be provided instead of ``validators`` ) + :param chain_validation: Whenever ``error_handler`` should perform validation too in + case it's a controller action or not. By default it's disabled. The first positional parameter can either be a dictonary of validators, a FormEncode schema validator, or a callable which acts like a FormEncode validator. - """ - def __init__(self, validators=None, error_handler=None, form=None): - super(validate, self).__init__(validators or form, error_handler) + def __init__(self, validators=None, error_handler=None, form=None, chain_validation=False): + super(validate, self).__init__(validators or form, error_handler, chain_validation) def __call__(self, func): deco = Decoration.get_decoration(func) @@ -630,7 +618,12 @@ def run_hook(self, remainder, params): if self._format == 'json' and request.content_type == 'application/json': - params.update(request.json_body) + try: + params.update(request.json_body) + except ValueError: + # Invalid JSON provided, nothing to decode + log.debug('Invalid JSON provided to decode_params') + pass def __call__(self, func): decoration = Decoration.get_decoration(func) diff -Nru turbogears2-2.3.7/tg/error.py turbogears2-2.3.12/tg/error.py --- turbogears2-2.3.7/tg/error.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/error.py 2018-04-06 11:31:55.000000000 +0000 @@ -48,7 +48,10 @@ The available options for **EMail** reporter are: + - ``trace_errors.enable`` -> Enable or disable error reporting, + by default is enabled if backlash is available and ``debug=false`` - ``trace_errors.smtp_server`` -> SMTP Server to connect to for sending emails + - ``trace_errors.smtp_port`` -> SMTP port to connect to - ``trace_errors.from_address`` -> Address sending the error emails - ``trace_errors.error_email`` -> Address the error emails should be sent to. - ``trace_errors.smtp_username`` -> Username to authenticate on SMTP server. @@ -70,7 +73,7 @@ """ - if not asbool(global_conf.get('debug')): + if errorware.get('enable', True) and not asbool(global_conf.get('debug')): reporters = [] @@ -104,7 +107,8 @@ All the options available for error reporting are configured as ``trace_slowreqs.*`` options in your ``app_cfg`` or ``.ini`` files: - - ``trace_slowreqs.enable`` -> Enable/Disable slow requests reporting + - ``trace_slowreqs.enable`` -> Enable/Disable slow requests reporting, + by default it's disabled. - ``trace_slowreqs.interval`` -> Report requests slower than this value (default: 25s) - ``trace_slowreqs.exclude`` -> List of urls that should be excluded diff -Nru turbogears2-2.3.7/tg/__init__.py turbogears2-2.3.12/tg/__init__.py --- turbogears2-2.3.7/tg/__init__.py 2014-10-03 19:43:50.000000000 +0000 +++ turbogears2-2.3.12/tg/__init__.py 2018-04-06 11:31:55.000000000 +0000 @@ -16,10 +16,8 @@ already been deployed, used, and tested, and were known to be "production ready." -TurboGears2 returns to that philosophy. It is built on Pylons, but it brings -a best-of-breed approach to Pylons. TurboGears 2 is commited to the following -Python components and libraries, which are backwards compatable with -TurboGears 1.1: +TurboGears 2 is committed to the following Python components and libraries, +which are backwards compatible with TurboGears 1.1: * Models: SQLAlchemy * Template engines: Genshi @@ -36,16 +34,12 @@ Mark Ramm described the relationship between TurboGears and Pylons this way "TurboGears 2 is to Pylons as Ubuntu is to Debian." -In other words we're focused on user experience, and creating a novice-friendly -environment. We ship a smaller subset of components, and thus are better able +While TurboGears2 is not based on Pylons anymore it's still focused on user experience, +and creating a novice-friendly environment. +We ship a smaller subset of components, and thus are better able to focus, test, and document things so that new users have the best possible experience. -Meanwhile Pylons provides the power and flexibility of the underlying core. - -And like Ubuntu, we don't intend to hide that power and flexibility from -advanced users, but we know that they want things set up to just work too. - Sensible defaults encourage code re-use within TurboGears because they make it possible for a group of TurboGears components to share assumptions about how things will work. @@ -53,7 +47,7 @@ """ from tg.request_local import app_globals, request, response, tmpl_context, session, cache, translator -from tg.configuration import config, AppConfig +from tg.configuration import config, AppConfig, milestones from tg.wsgiapp import TGApp from tg.controllers import TGController, RestController, redirect, url, lurl, abort from tg.release import version @@ -76,4 +70,4 @@ 'require', 'response', 'session', 'TGApp', 'TGController', 'tmpl_context', 'use_wsgi_app', 'validate', 'i18n','json_encode', 'cache', 'url', 'lurl', 'dispatched_controller', 'use_custom_format', 'with_engine', 'render_template', - 'Request', 'Response', 'cached', 'decode_params'] + 'Request', 'Response', 'cached', 'decode_params', 'milestones'] diff -Nru turbogears2-2.3.7/tg/jsonify.py turbogears2-2.3.12/tg/jsonify.py --- turbogears2-2.3.7/tg/jsonify.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/jsonify.py 2018-04-06 11:31:55.000000000 +0000 @@ -39,7 +39,8 @@ """ CONFIG_NAMESPACE = 'json.' - CONFIG_OPTIONS = {'isodates': asbool} + CONFIG_OPTIONS = {'isodates': asbool, + 'allow_lists': asbool} def __init__(self, **kwargs): self._registered_types_map = {} @@ -48,16 +49,20 @@ kwargs = self.configure(**kwargs) super(JSONEncoder, self).__init__(**kwargs) - def configure(self, isodates=False, custom_encoders=None, **kwargs): + def configure(self, isodates=False, custom_encoders=None, allow_lists=False, **kwargs): """JSON encoder can be configured through :class:`.AppConfig` (``app_cfg.base_config``) using the following options: - ``json.isodates`` -> encode dates using ISO8601 format - ``json.custom_encoders`` -> List of tuples ``(type, encode_func)`` to register custom encoders for specific types. + - ``json.allow_lists`` -> Allows lists to be encoded, this is usually disabled for + security reasons due to JSON hijacking. See http://stackoverflow.com/questions/16289894 + for additional details. """ self._isodates = isodates + self._allow_lists = allow_lists if custom_encoders is not None: for type_, encoder in custom_encoders.items(): self.register_custom_encoder(type_, encoder) @@ -86,9 +91,9 @@ return encoder(obj) elif hasattr(obj, '__json__') and callable(obj.__json__): return obj.__json__() - elif isinstance(obj, (datetime.date, datetime.datetime)): + elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)): if self._isodates: - if isinstance(obj, datetime.datetime): + if isinstance(obj, (datetime.datetime, datetime.time)): obj = obj.replace(microsecond=0) return obj.isoformat() else: @@ -128,13 +133,14 @@ if isinstance(obj, string_type): return encode_func(obj) - try: - value = obj['test'] - except TypeError: - if not hasattr(obj, '__json__') and not is_saobject(obj) and not is_mingobject(obj): - raise JsonEncodeError('Your Encoded object must be dict-like.') - except: - pass + if encoder._allow_lists is False: + try: + value = obj['test'] + except TypeError: + if not hasattr(obj, '__json__') and not is_saobject(obj) and not is_mingobject(obj): + raise JsonEncodeError('Your Encoded object must be dict-like.') + except: + pass return encode_func(obj) diff -Nru turbogears2-2.3.7/tg/release.py turbogears2-2.3.12/tg/release.py --- turbogears2-2.3.7/tg/release.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/release.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,5 +1,5 @@ """TurboGears project related information""" -version = "2.3.7" +version = "2.3.12" description = "Next generation TurboGears" long_description=""" TurboGears brings together a best of breed python tools @@ -24,8 +24,7 @@ https://github.com/TurboGears """ url="http://www.turbogears.org/" -author= "Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, Michael Pedersen, Alessandro Molina, and the TurboGears community" -email = "mark.ramm@gmail.com, alberto@toscat.net, m.pedersen@icelus.org, amol@turbogears.org" -copyright = """Copyright 2005-2015 Kevin Dangoor, -Alberto Valverde, Mark Ramm, Christopher Perkins and contributors""" +author= "Alessandro Molina, Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, Michael Pedersen and the TurboGears community" +email = "amol@turbogears.org" +copyright = """Copyright 2005-2018 Kevin Dangoor, Alberto Valverde, Mark Ramm, Christopher Perkins, Alessandro Molina and contributors""" license = "MIT" diff -Nru turbogears2-2.3.7/tg/renderers/genshi.py turbogears2-2.3.12/tg/renderers/genshi.py --- turbogears2-2.3.7/tg/renderers/genshi.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/renderers/genshi.py 2018-04-06 11:31:55.000000000 +0000 @@ -31,6 +31,12 @@ - ``templating.genshi.name_constant_patch`` -> Enable/Disable patch for Python3.4 compatibility. - ``templating.genshi.max_cache_size`` -> Maximum number of templates to keep cached, by default 30. - ``templating.genshi.method`` -> Genshi rendering method (html or xhtml). + + Supported ``render_params``: + + - Caching options supported by :func:`.cached_template` + - ``doctype`` -> To override the global doctype + - ``method`` -> To override the global rendering method """ CONFIG_OPTIONS = { 'max_cache_size': asint, @@ -100,10 +106,12 @@ TemplateLoader = GenshiTemplateLoader template_loader_args = {} - loader = TemplateLoader(search_path=config.paths.templates, - max_cache_size=options.get('max_cache_size', - asint(config.get('genshi.max_cache_size', 30))), - auto_reload=config.auto_reload_templates, + loader = TemplateLoader(search_path=config['paths'].templates, + max_cache_size=options.get( + 'max_cache_size', + asint(config.get('genshi.max_cache_size', 30)) + ), + auto_reload=config['auto_reload_templates'], callback=cls.on_template_loaded, **template_loader_args) diff -Nru turbogears2-2.3.7/tg/renderers/jinja.py turbogears2-2.3.12/tg/renderers/jinja.py --- turbogears2-2.3.7/tg/renderers/jinja.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/renderers/jinja.py 2018-04-06 11:31:55.000000000 +0000 @@ -43,28 +43,28 @@ template_loader_args = {} if not 'jinja_extensions' in config: - config.jinja_extensions = [] + config['jinja_extensions'] = [] # Add i18n extension by default - if not "jinja2.ext.i18n" in config.jinja_extensions: - config.jinja_extensions.append("jinja2.ext.i18n") + if not "jinja2.ext.i18n" in config['jinja_extensions']: + config['jinja_extensions'].append("jinja2.ext.i18n") if not 'jinja_filters' in config: - config.jinja_filters = {} + config['jinja_filters'] = {} loader = ChoiceLoader( - [TemplateLoader(path, **template_loader_args) for path in config.paths['templates']]) + [TemplateLoader(path, **template_loader_args) for path in config['paths']['templates']]) jinja2_env = Environment(loader=loader, autoescape=True, - auto_reload=config.auto_reload_templates, - extensions=config.jinja_extensions) + auto_reload=config['auto_reload_templates'], + extensions=config['jinja_extensions']) # Try to load custom filters module under app_package.lib.templatetools try: - if not config.package_name: + if not config['package_name']: raise AttributeError() - filter_package = config.package_name + ".lib.templatetools" + filter_package = config['package_name'] + ".lib.templatetools" autoload_lib = __import__(filter_package, {}, {}, ['jinja_filters']) try: autoload_filters = dict( @@ -81,7 +81,7 @@ # Add jinja filters filters = dict(FILTERS, **autoload_filters) - filters.update(config.jinja_filters) + filters.update(config['jinja_filters']) jinja2_env.filters = filters # Jinja's unable to request c's attributes without strict_c diff -Nru turbogears2-2.3.7/tg/renderers/json.py turbogears2-2.3.12/tg/renderers/json.py --- turbogears2-2.3.7/tg/renderers/json.py 2014-10-03 19:43:51.000000000 +0000 +++ turbogears2-2.3.12/tg/renderers/json.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,7 +1,5 @@ import tg -from tg.support.converters import asbool -from tg.configuration.utils import coerce_config -from tg.jsonify import encode, JSONEncoder, _default_encoder +from tg.jsonify import encode, JSONEncoder from .base import RendererFactory from tg.exceptions import HTTPBadRequest @@ -9,6 +7,17 @@ class JSONRenderer(RendererFactory): + """ + JSON rendering can be configured using options supported by :meth:`.JSONEncoder.configure` + + Supported ``render_params``: + + - All supported by :meth:`.JSONEncoder.configure` + - ``key`` -> Render a single key of the dictionary returned by controller + instead of rendering the dictionary itself. + - ``callback_param`` -> Name of the callback to call in rendered JS for **jsonp** + + """ engines = {'json': {'content_type': 'application/json'}, 'jsonp': {'content_type': 'application/javascript'}} with_tg_vars = False @@ -31,17 +40,25 @@ return lambda obj: encode(obj, JSONEncoder(**options)) @staticmethod - def render_json(template_name, template_vars, **kwargs): - encode = JSONRenderer._get_configured_encode(kwargs) + def render_json(template_name, template_vars, **render_params): + key = render_params.pop('key', None) + if key is not None: + template_vars = template_vars[key] + + encode = JSONRenderer._get_configured_encode(render_params) return encode(template_vars) @staticmethod - def render_jsonp(template_name, template_vars, **kwargs): - pname = kwargs.pop('callback_param', 'callback') + def render_jsonp(template_name, template_vars, **render_params): + key = render_params.pop('key', None) + if key is not None: + template_vars = template_vars[key] + + pname = render_params.pop('callback_param', 'callback') callback = tg.request.GET.get(pname) if callback is None: raise HTTPBadRequest('JSONP requires a "%s" parameter with callback name' % pname) - encode = JSONRenderer._get_configured_encode(kwargs) + encode = JSONRenderer._get_configured_encode(render_params) values = encode(template_vars) return '%s(%s);' % (callback, values) \ No newline at end of file diff -Nru turbogears2-2.3.7/tg/renderers/kajiki.py turbogears2-2.3.12/tg/renderers/kajiki.py --- turbogears2-2.3.7/tg/renderers/kajiki.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/renderers/kajiki.py 2018-04-06 11:31:55.000000000 +0000 @@ -30,6 +30,13 @@ - ``templating.kajiki.xml_autoblocks`` -> List of tags that should be automatically converted to blocks. - ``templating.kajiki.cdata_scripts`` -> Automatically wrap scripts in CDATA. - ``templating.kajiki.html_optional_tags`` -> Allow unclosed html, head and body tags. + - ``templating.kajiki.strip_text`` -> Strip leading/trailing spaces from text nodes. + + Supported ``render_params``: + + - Caching options supported by :func:`.cached_template` + - All arguments supported by :func:`kajiki.xml_template.XMLTemplate` + """ CONFIG_OPTIONS = { 'force_mode': str, @@ -38,6 +45,7 @@ 'xml_autoblocks': aslist, 'cdata_scripts': asbool, 'html_optional_tags': asbool, + 'strip_text': asbool } engines = {'kajiki': {'content_type': 'text/html'}} @@ -62,9 +70,9 @@ from kajiki import i18n i18n.gettext = ugettext - loader = KajikiTemplateLoader(config.paths.templates[0], + loader = KajikiTemplateLoader(config['paths'].templates[0], dotted_finder=app_globals.dotted_filename_finder, - reload=config.auto_reload_templates, + reload=config['auto_reload_templates'], **options) return {'kajiki': cls(loader)} @@ -72,7 +80,7 @@ self.loader = loader def __call__(self, template_name, template_vars, cache_key=None, - cache_type=None, cache_expire=None): + cache_type=None, cache_expire=None, **render_params): """Render a template with Kajiki Accepts the cache options ``cache_key``, ``cache_type``, and @@ -82,7 +90,7 @@ # Create a render callable for the cache function def render_template(): # Grab a template reference - template = self.loader.load(template_name) + template = self.loader.load(template_name, **render_params) return Markup(template(template_vars).render()) return cached_template(template_name, render_template, @@ -110,4 +118,7 @@ if not os.path.exists(filename): raise IOError('Template %s not found' % filename) - return super(KajikiTemplateLoader, self)._filename(filename) + resolved_filename = super(KajikiTemplateLoader, self)._filename(filename) + if resolved_filename is None: + raise IOError('Template %s not found in template paths' % filename) + return resolved_filename diff -Nru turbogears2-2.3.7/tg/renderers/mako.py turbogears2-2.3.12/tg/renderers/mako.py --- turbogears2-2.3.7/tg/renderers/mako.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/renderers/mako.py 2018-04-06 11:31:55.000000000 +0000 @@ -34,12 +34,14 @@ """ Configuration Options available as ``templating.mako.*``: + - ``templating.mako.template_extension`` -> Mako Templates extension, default ``.mak`` - ``templating.mako.compiled_templates_dir`` -> Where to store mako precompiled templates. By default templates are only stored in memory and not on disk. """ #: Configuration Options that can be set as ``templating.mako.*``. CONFIG_OPTIONS = { - 'compiled_templates_dir': str + 'compiled_templates_dir': str, + 'template_extension': str } engines = {'mako': {'content_type': 'text/html'}} @@ -80,46 +82,53 @@ "o.compiled_templates_dir` configuration option to a " "writable directory." % bad_path) - dotted_finder = app_globals.dotted_filename_finder - if use_dotted_templatenames: - # Support dotted names by injecting a slightly different template - # lookup system that will return templates from dotted template notation. - mako_lookup = DottedTemplateLookup( - input_encoding='utf-8', output_encoding='utf-8', - imports=['from markupsafe import escape_silent as escape'], - package_name=config.package_name, - dotted_finder=dotted_finder, - module_directory=compiled_dir, - default_filters=['escape'], - auto_reload_templates=config.auto_reload_templates) + template_extension = options.get('template_extension', '.mak') - else: - mako_lookup = TemplateLookup( - directories=config.paths['templates'], - module_directory=compiled_dir, - input_encoding='utf-8', output_encoding='utf-8', - imports=['from markupsafe import escape_silent as escape'], - default_filters=['escape'], - filesystem_checks=config.auto_reload_templates) - - return {'mako': cls(dotted_finder, mako_lookup, use_dotted_templatenames)} - - def __init__(self, dotted_finder, mako_lookup, use_dotted_templatenames): - self.dotted_finder = dotted_finder - self.loader = mako_lookup + # Support dotted names by using a slightly different template + # lookup system that will return templates from dotted template notation. + dotted_loader = DottedTemplateLookup( + input_encoding='utf-8', output_encoding='utf-8', + imports=['from markupsafe import escape_silent as escape'], + package_name=config['package_name'], + find_template_file=lambda t: app_globals.dotted_filename_finder.get_dotted_filename( + t, template_extension=template_extension + ), + template_extension=template_extension, + module_directory=compiled_dir, + default_filters=['escape'], + auto_reload_templates=config['auto_reload_templates']) + + normal_loader = TemplateLookup( + directories=config['paths']['templates'], + module_directory=compiled_dir, + input_encoding='utf-8', output_encoding='utf-8', + imports=['from markupsafe import escape_silent as escape'], + default_filters=['escape'], + filesystem_checks=config['auto_reload_templates']) + + return {'mako': cls(use_dotted_templatenames, template_extension, + dotted_loader, normal_loader)} + + def __init__(self, use_dotted_templatenames, template_extension, + dotted_loader, normal_loader): + self.dotted_loader = dotted_loader + self.normal_loader = normal_loader self.use_dotted_templatenames = use_dotted_templatenames + self.template_extension = template_extension def __call__(self, template_name, template_vars, cache_key=None, cache_type=None, cache_expire=None): - if self.use_dotted_templatenames: - template_name = self.dotted_finder.get_dotted_filename(template_name, - template_extension='.mak') + if self.use_dotted_templatenames and not template_name.endswith(self.template_extension): + template_name = self.dotted_loader.find_template_file(template_name) + loader = self.dotted_loader + else: + loader = self.normal_loader # Create a render callable for the cache function def render_template(): # Grab a template reference - template = self.loader.get_template(template_name) + template = loader.get_template(template_name) return Markup(template.render_unicode(**template_vars)) return cached_template(template_name, render_template, cache_key=cache_key, @@ -148,11 +157,12 @@ def __init__(self, input_encoding, output_encoding, imports, default_filters, package_name, - dotted_finder, module_directory=None, + find_template_file, template_extension='.mak', + module_directory=None, auto_reload_templates=False): self.package_name = package_name - self.dotted_finder = dotted_finder + self.find_template_file = find_template_file self.input_encoding = input_encoding self.output_encoding = output_encoding @@ -164,6 +174,7 @@ self.template_filenames_cache = dict() self.module_directory = module_directory self.auto_reload = auto_reload_templates + self.template_extension = template_extension # a mutex to ensure thread safeness during template loading self._mutex = threading.Lock() @@ -179,13 +190,12 @@ if uri.startswith('local:'): uri = self.package_name + '.' + uri[6:] - if '.' in uri: + if '.' in uri and not uri.endswith(self.template_extension): # We are in the DottedTemplateLookup system so dots in # names should be treated as a Python path. Since this # method is called by template inheritance we must # support dotted names also in the inheritance. - result = self.dotted_finder.get_dotted_filename(template_name=uri, - template_extension='.mak') + result = self.find_template_file(uri) if not uri in self.template_filenames_cache: # feed our filename cache if needed. diff -Nru turbogears2-2.3.7/tg/render.py turbogears2-2.3.12/tg/render.py --- turbogears2-2.3.7/tg/render.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/render.py 2018-04-06 11:31:55.000000000 +0000 @@ -105,6 +105,9 @@ _=tg.i18n.ugettext, N_=tg.i18n.gettext_noop) + # If there is an identity, push it to the Pylons template context + tmpl_context.identity = tg_vars['identity'] + # Allow users to provide a callable that defines extra vars to be # added to the template namespace variable_provider = conf.get('variable_provider', None) @@ -176,20 +179,13 @@ """ config = tg.config._current_obj() - render_function = None - if template_engine is not None: - # the engine was defined in the @expose() - render_function = config['render_functions'].get(template_engine) - - if render_function is None: - # engine was forced in @expose() but is not present in the - # engine list, warn developer - raise MissingRendererError(template_engine) - - if not render_function: - # getting the default renderer, if no engine was defined in @expose() + if template_engine is None: template_engine = config['default_renderer'] - render_function = config['render_functions'][template_engine] + + render_function = config['render_functions'].get(template_engine) + if render_function is None: + # engine is not present in the engine list, warn developer + raise MissingRendererError(template_engine) if not template_vars: template_vars = {} diff -Nru turbogears2-2.3.7/tg/request_local.py turbogears2-2.3.12/tg/request_local.py --- turbogears2-2.3.7/tg/request_local.py 2015-04-28 20:01:51.000000000 +0000 +++ turbogears2-2.3.12/tg/request_local.py 2018-04-06 11:31:55.000000000 +0000 @@ -28,6 +28,9 @@ compatibility with paste.wsgiwrappers.WSGIRequest. """ + def _fast_setattr(self, name, value): + object.__setattr__(self, name, value) + def languages_best_match(self, fallback=None): al = self.accept_language try: @@ -48,19 +51,30 @@ @cached_property def controller_state(self): + warnings.warn("request.controller_state is now deprecated, please use" + "request.dispatch_state to access DispatchState for current request.", + DeprecationWarning, stacklevel=2) + return self._controller_state + + @cached_property + def dispatch_state(self): + """Details and info about dispatcher that handled this request.""" return self._controller_state @cached_property def controller_url(self): + """Url of the current controller.""" state = self._controller_state return '/'.join(state.path[:-len(state.remainder)]) @cached_property def plain_languages(self): + """Return the list of browser preferred languages""" return self.languages_best_match() @cached_property def languages(self): + """Return the list of browser preferred languages ensuring that ``i18n.lang`` is listed.""" return self.languages_best_match(self._language) @property @@ -79,10 +93,21 @@ @property def response_type(self): + """Expected response content type when URL Extensions are enabled. + + In case URL Request Extension is enabled this will be the content type + of the expected response. ``disable_request_extensions`` drives + this is enabled or not. + """ return self._response_type @property def response_ext(self): + """URL extension when URL Extensions are enabled. + + In case URL Request Extension is enabled this will be the extension of the url. + ``disable_request_extensions`` drives this is enabled or not. + """ return self._response_ext def match_accept(self, mimetypes): @@ -115,6 +140,10 @@ @cached_property def args_params(self): + """Arguments used for dispatching the request. + + This mixes GET and POST arguments. + """ # This was: dict(((str(n), v) for n,v in self.params.mixed().items())) # so that keys were all strings making possible to use them as arguments. # Now it seems that all keys are always strings, did WebOb change behavior? @@ -122,11 +151,24 @@ @property def quoted_path_info(self): + """PATH used for dispatching the request.""" bpath = webob_bytes_(self.path_info, self.url_encoding) return webob_url_quote(bpath, PATH_SAFE) - def _fast_setattr(self, name, value): - object.__setattr__(self, name, value) + def disable_error_pages(self): + """Disable custom error pages for the current request. + + This will forward your response as is bypassing the :class:`.ErrorPageApplicationWrapper` + """ + self.environ['tg.status_code_redirect'] = False + + def disable_auth_challenger(self): + """Disable authentication challenger for current request. + + This will forward your response as is in case of 401 bypassing any + repoze.who challenger. + """ + self.environ['tg.skip_auth_challenge'] = True class Response(WebObResponse): @@ -152,6 +194,7 @@ cookie_value = sig + base64.encodestring(pickled) self.set_cookie(name, cookie_value, **kwargs) + config = DispatchingConfig() context = StackedObjectProxy(name="context") diff -Nru turbogears2-2.3.7/tg/util/files.py turbogears2-2.3.12/tg/util/files.py --- turbogears2-2.3.7/tg/util/files.py 2015-10-13 12:56:20.000000000 +0000 +++ turbogears2-2.3.12/tg/util/files.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,6 +1,9 @@ -import os +import contextlib +import os, sys import re -from pkg_resources import resource_filename +import uuid + +from pkg_resources import resource_filename, resource_stream, get_default_cache from .._compat import unicode_text, PY2 @@ -28,42 +31,73 @@ Given a string containing the file/template name passed to the @expose decorator we will return a resource useable as a filename even - if the file is in fact inside a zipped egg. + if the file is in fact inside a zipped egg or in a frozen library. The actual implementation is a revamp of the Genshi buffet support plugin, but could be used with any kind a file inside a python package. - @param template_name: the string representation of the template name - as it has been given by the user on his @expose decorator. - Basically this will be a string in the form of: - "genshi:myapp.templates.somename" - @type template_name: string - - @param template_extension: the extension we excpect the template to have, - this MUST be the full extension as returned by the os.path.splitext - function. This means it should contain the dot. ie: '.html' - - This argument is optional and the default value if nothing is provided will - be '.html' - @type template_extension: string + :param template_name: the string representation of the template name + as it has been given by the user on his @expose decorator. + Basically this will be a string in the form of: + `"myapp.templates.somename"` + :type template_name: str + + :param template_extension: the extension we excpect the template to have, + this MUST be the full extension as returned by + the os.path.splitext function. + This means it should contain the dot. ie: '.html' + This argument is optional and the default + value if nothing is provided will be '.html' + :type template_extension: str + + The ``template_name`` parameter also accepts a form with explicit extension + ``myapp.templates.somename!xhtml`` that will override the ``template_exstesion`` + argument and will always use ``.xhtml`` as the extension. This is usually + convenient in extensions and libraries that expose a template and want to + ensure they work even in the case the application using them has a different + extension for templates on the same engine. """ + cache_key = template_name try: - return self.__cache[template_name] + return self.__cache[cache_key] except KeyError: # the template name was not found in our cache + try: + # Allow for the package.file!ext syntax + template_name, template_extension = template_name.rsplit('!', 1) + template_extension = '.' + template_extension + except ValueError: + pass + divider = template_name.rfind('.') if divider >= 0: package = template_name[:divider] - basename = template_name[divider + 1:] + template_extension + basename = template_name[divider + 1:] + resourcename = basename + template_extension try: - result = resource_filename(package, basename) + result = resource_filename(package, resourcename) except ImportError as e: - raise DottedFileLocatorError(str(e) +". Perhaps you have forgotten an __init__.py in that folder.") + raise DottedFileLocatorError( + "%s. Perhaps you have forgotten an __init__.py in that folder." % e + ) + except NotImplementedError: + # Cope with zipped files or py2exe apps + if not hasattr(self, '__temp_dir'): + self.__temp_dir = os.path.join(get_default_cache(), + 'tgdf-%s' % uuid.uuid1()) + + result = os.path.join(self.__temp_dir, package, resourcename) + if not os.path.isdir(os.path.dirname(result)): + os.makedirs(os.path.dirname(result)) + + with contextlib.closing(resource_stream(package, resourcename)) as rd: + with open(result, 'wb') as result_f: + result_f.write(rd.read()) else: result = template_name result = os.path.abspath(result) - self.__cache[template_name] = result + self.__cache[cache_key] = result return result diff -Nru turbogears2-2.3.7/tg/util/html.py turbogears2-2.3.12/tg/util/html.py --- turbogears2-2.3.7/tg/util/html.py 2015-07-25 23:52:50.000000000 +0000 +++ turbogears2-2.3.12/tg/util/html.py 2018-04-06 11:31:55.000000000 +0000 @@ -1,7 +1,11 @@ +from ..jsonify import JSONEncoder from ..jsonify import encode as json_encode -def script_json_encode(obj, **kwargs): +_script_json_encoder = JSONEncoder(isodates=True, allow_lists=True) + + +def script_json_encode(obj, encoder=_script_json_encoder, **kwargs): """Works exactly like :func:`tg.jsonify.encode` but is safe for use in ``