Package: pyramid-sanity
pyramid-sanitySensible defaults to catch bad behavior.pyramid-sanityis a Pyramid extension that catches certain crashes caused by
badly formed requests, turning them into400: Bad Requestresponses instead.It also prevents apps from returning HTTP redirects with badly encoded locations
that can crash WSGI servers.The aim is to have sensible defaults to make it easier to write a reliable Pyramid app.For details of all the errors and fixes, and how to reproduce them see:Error details.UsagewithConfigurator()asconfig:config.add_settings({# See the section below for all settings..."pyramid_sanity.check_form":False,})# Add this as near to the end of your config as possible:config.include("pyramid_sanity")By default all fixes are enabled. You can disable them individually with settings:config.add_settings({# Don't check for badly declared forms."pyramid_sanity.check_form":False})You can setpyramid_sanity.disable_alltoTrueto disable all of the fixes,
then enable only certain fixes one by one:config.add_settings({# Disable all fixes."pyramid_sanity.disable_all":True,# Enable only the badly encoded query params fix."pyramid_sanity.check_params":True})OptionsOptionDefaultEffectpyramid_sanity.disable_allFalseDisable all checks by defaultpyramid_sanity.check_formTrueCheck for badly declared formspyramid_sanity.check_paramsTrueCheck for badly encoded query paramspyramid_sanity.check_pathTrueCheck for badly encoded URL pathspyramid_sanity.ascii_safe_redirectsTrueSafely encode redirect locationsExceptionsAll exceptions returned bypyramid-sanityare subclasses ofpyramid_sanity.exceptions.SanityException, which is a subclass ofpyramid.httpexceptions.HTTPBadRequest.This means allpyramid-sanityexceptions trigger400: Bad Requestresponses.Different exception subclasses are returned for different problems, so you can
registercustom exception viewsto handle them if you want:ExceptionReturned forpyramid_sanity.exceptions.InvalidQueryStringBadly encoded query paramspyramid_sanity.exceptions.InvalidFormDataBad form postspyramid_sanity.exceptions.InvalidURLBadly encoded URL pathsTween orderingpyramid-sanityuses a number of Pyramidtweensto do its work. It's important that your app's tween chain has:Our tweens that check for errors in the request, firstOur tweens that check for errors in the output of your app, lastThe easiest way to achieve this is to includeconfig.include("pyramid_sanity")as late as possiblein your config. This uses Pyramid's"best effort" implicit tween orderingto add the tweens and should work as long as your app doesn't add any
more tweens, or include any extensions that add tweens, afterwards.You can to check the order of tweens in your app with Pyramid'sptweenscommand.
As long as there are no tweens which accessrequest.GETorrequest.POSTabove the input checking tweens, or generate redirects below output checking
tweens, you should be fine.You can force the order with Pyramid'sexplicit tween orderingif you need to.Tweens that raise non-ASCII redirectspyramid-sanityprotects against non-ASCII redirects raised by your app's
views by safely encoding them, but it can't protect againstother tweensthatraisenon-ASCII redirects.For example this tween might cause a WSGI server (like Gunicorn) that's serving
your app to crash withUnicodeEncodeError:defnon_ascii_redirecting_tween_factory(handler,registry):defnon_ascii_redirecting_tween(request):frompyramid.httpexceptionsimportHTTPFoundraiseHTTPFound(location="http://example.com/€/☃")returnnon_ascii_redirecting_tweenYou'll just have to make sure that your app doesn't have any tweens that do this!
Tweens should encode any redirect locations that they generate,like this.Error detailsIf you would like to reproduce the errors anexample appis given at the end
of this section. All of the presentedcurlcommands work with this app.Badly encoded query parameters makesrequest.GETcrashcurl 'http://localhost:6543/foo?q=%FC'By defaultWebOb raisesUnicodeDecodeError. As there is no built-in exception view for
this exception the app crashes.Withpyramid-sanityApyramid_sanity.exceptions.InvalidQueryStringis returned which results in a400: Bad Requestresponse.Related issues:https://github.com/Pylons/pyramid/issues/3399https://github.com/Pylons/webob/issues/161A badly encoded path can cause a crashcurl 'http://localhost:6543/%FC'By defaultPyramid raisespyramid.exceptions.URLDecodeError.
As there is no built-in exception view for this exception the app crashes.Withpyramid-sanityApyramid_sanity.exceptions.InvalidURLis returned which results in a400: Bad Requestresponse.Related issueshttps://github.com/Pylons/pyramid/issues/434https://github.com/Pylons/pyramid/issues/1374https://github.com/Pylons/pyramid/issues/2047https://github.com/Pylons/webob/issues/114Bad or missing multipart boundary declarations makerequest.POSTcrashcurl --request POST --url http://localhost:6543/foo --header 'content-type: multipart/form-data'By defaultWebOb raises an uncaughtValueError. As there is no built-in exception view
for this exception the app crashes.Withpyramid-sanityApyramid_sanity.exceptions.InvalidFormDatais returned which results in a400: Bad Requestresponse.Related issues:https://github.com/Pylons/pyramid/issues/1258Issuing redirects containing a non-ASCII location crashes the WSGI servercurl http://localhost:6543/redirectBy defaultThe app will emit the redirect successfully, but the WSGI server running the app
may crash. With the example app belowwsgiref.simple_serverraises an
uncaughtAttributeError.Withpyramid-sanityThe redirect is safely URL encoded.Addendum: Example applicationfromwsgiref.simple_serverimportmake_serverfrompyramid.configimportConfiguratorfrompyramid.responseimportResponsefrompyramid.httpexceptionsimportHTTPFounddefredirect(request):# Return a redirect to a URL with a non-ASCII character in it.returnHTTPFound(location="http://example.com/☃")defhello_world(request):returnResponse(f"Hello World! Query string was:{request.GET}. Form body was:{request.POST}")if__name__=="__main__":withConfigurator()asconfig:config.add_route("redirect","/redirect")config.add_route("hello","/{anything}")config.add_view(hello_world,route_name="hello")config.add_view(redirect,route_name="redirect")app=config.make_wsgi_app()server=make_server("0.0.0.0",6543,app)server.serve_forever()Setting up Your pyramid-sanity Development EnvironmentFirst you'll need to install:Git.
On Ubuntu:sudo apt install git, on macOS:brew install git.GNU Make.
This is probably already installed, runmake --versionto check.pyenv.
Follow the instructions in pyenv's README to install it.
TheHomebrewmethod works best on macOS.
TheBasic GitHub Checkoutmethod works best on Ubuntu.
Youdon'tneed to set up pyenv's shell integration ("shims"), you canuse pyenv without shims.Then to set up your development environment:git clone https://github.com/hypothesis/pyramid-sanity.git
cd pyramid_sanity
make helpReleasing a New Version of the ProjectFirst, to get PyPI publishing working you need to go to:https://github.com/organizations/hypothesis/settings/secrets/actions/PYPI_TOKENand add pyramid-sanity to thePYPI_TOKENsecret's selected
repositories.Now that the pyramid-sanity project has access to thePYPI_TOKENsecret
you can release a new version by justcreating a new GitHub release.
Publishing a new GitHub release will automatically triggera GitHub Actions workflowthat will build the new version of your Python package and upload it tohttps://pypi.org/project/pyramid-sanity.Changing the Project's Python VersionsTo change what versions of Python the project uses:Change the Python versions in thecookiecutter.jsonfile. For example:"python_versions":"3.10.4, 3.9.12",Re-run the cookiecutter template:make templateCommit everything to git and send a pull requestChanging the Project's Python DependenciesTo change the production dependencies in thesetup.cfgfile:Change the dependencies in the.cookiecutter/includes/setuptools/install_requiresfile.
If this file doesn't exist yet create it and add some dependencies to it.
For example:pyramid
sqlalchemy
celeryRe-run the cookiecutter template:make templateCommit everything to git and send a pull requestTo change the project's formatting, linting and test dependencies:Change the dependencies in the.cookiecutter/includes/tox/depsfile.
If this file doesn't exist yet create it and add some dependencies to it.
Use tox'sfactor-conditional settingsto limit which environment(s) each dependency is used in.
For example:lint: flake8,
format: autopep8,
lint,tests: pytest-faker,Re-run the cookiecutter template:make templateCommit everything to git and send a pull request