possible weird bug in pyramid web framework - pyramid

I was following pyramid web framework tutorial steps given in the link:
https://docs.pylonsproject.org/projects/pyramid/en/latest/quick_tutorial/cookiecutters.html
After setting it up and visiting http://localhost:6543/
everything works as expected with the project name "Pyramid scaffold" in the route name showing properly.
Then I added a second view function and added it to the route. But then the home route starts showing 404.
The second route works, but the first route and view stop working and gives 404 when loaded in the browser.
I cannot find what the issue is. After adding several functions and routes, I was not able to find the issue.
I am thinking this is some issue with provided cookiecutter or pyramid framework itself.
This never used to happen with pyramid version less than 2. Also tried adding different views and routes. Only one route seems to work and all others return 404 exception.
No files were deleted or edited other than the ones listed here.
Can someone please help me with this?
Original contents of files
# File location 'views/default.py"
from pyramid.view import view_config
#view_config(route_name='home', renderer='pyramid_scaffold:templates/mytemplate.jinja2')
def my_view(request):
return {'project': 'Pyramid Scaffold'}
and
# File location 'routes.py"
def includeme(config):
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
After my changes
# File location 'views/default.py"
from pyramid.view import view_config
#view_config(route_name='home', renderer='pyramid_scaffold:templates/mytemplate.jinja2')
def my_view(request):
return {'project': 'Pyramid Scaffold'}
#view_config(route_name='second', renderer='pyramid_scaffold:templates/mytemplate.jinja2')
def my_view(request):
return {'project': 'this works'}
and
# File location 'routes.py"
def includeme(config):
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_route('home', '/')
config.add_route('second', '/second')
error log in terminal:
2021-10-01 02:41:29,880 INFO [pyramid_debugtoolbar:287][waitress-0] Squashed pyramid.httpexceptions.HTTPNotFound at http://localhost:6543/
traceback url: http://localhost:6543/_debug_toolbar/313430323330373531313831343038/exception
When visiting traceback URL, no helpful info other than saying
env/lib/python3.8/site-packages/pyramid/router.py", line 169, in handle_request
raise HTTPNotFound(msg)

Bugs are almost always in the developer's code, and rarely in a mature package such as Pyramid.
In your case, you defined two methods with the same name, overriding the first with the second. Therefore the view for the first route home was removed.
To remedy the situation, give the second view function a unique name.
#view_config(route_name='second', renderer='pyramid_scaffold:templates/mytemplate.jinja2')
def my_second_view(request):
return {'project': 'this works'}

Related

Is it possible to dynamically change log file location based on route in flask?

I have a flask app with various routes setup.
I want to log messages to go to a different log file for each route.
Is this possible?
In the function that responds to the request on each route I have tried doing something like this (note this is just pseudo-code for sake of example):
#app.route('/routeA', methods=['POST'])
def routeA():
logging.basicConfig(filename='/path/to/routeA.log'))
...
#app.route('/routeB', methods=['POST'])
def routeB():
logging.basicConfig(filename='/path/to/routeB.log'))
...
#app.route('/routeC', methods=['POST'])
def routeC():
logging.basicConfig(filename='/path/to/routeC.log'))
...
Which I would hope means that requests to routeC go to RouteC.log and similar for A and B
BUT if after starting uwsgi I hit /routeC first this seems to set up all logs to got to routeC.log such that when I then hit routes A or B their log lines also end up in the routeC.log
Similar occurs if I hit routeB or routeA first - whichever gets the first request establishes logging to that file and the other routes don't seem to be able to override that for their requests.
Is this kind of dynamic setting log file location depending on request actually possible?
If so what am I doing wrong?
I feel like I'm missing something basic here...

Flask_Sqlalchemy with multithreaded Apache. Sessions out of sync with database

Background: Apache server using mod_wsgi to serve a Flask app using Flask_Sqlalchemy connecting to MySQL. This is a full stack application so it is nearly impossible to create a minimal example but I have tried.
My problem is that when I make some change that should modify the database subsequent requests don't always seem to reflect that change. For example if I create an object, then try to edit that same object, the edit will sometimes fail.
Most of the time if I create an object then go to the page listing all the objects, it will not show up on the list. Sometimes it will show up until I refresh, when it will disappear, and with another refresh it shows up again.
The same happens with edits. Example code:
bp = Blueprint('api_region', __name__, url_prefix='/app/region')
#bp.route('/rename/<int:region_id>/<string:name>', methods=['POST'])
def change_name(region_id, name):
region = Region.query.get(region_id)
try:
region.name = name
except AttributeError:
abort(404)
db.session.add(region)
db.session.commit()
return "Success"
#bp.route('/name/<int:region_id>/', methods=['GET'])
def get_name(region_id):
region = Region.query.get(region_id)
try:
name = region.name
except AttributeError:
abort(404)
return name
After object is created send a POST
curl -X POST https://example.com/app/region/rename/5/Europe
Then several GETs
curl -X GET https://example.com/app/region/name/5/
Sometimes, the GET will return the correct info, but every now and then it will return whatever it was before. Further example output https://pastebin.com/s8mqRHSR it happens at varying frequency but about one in 25 will fail, and it isn't always the "last" value either, when testing it seems to get 'stuck' at a certain value no matter how many times I change it up.
I am using the "dynamically bound" example of Flask_Sqlalchemy
db = SQLAlchemy()
def create_app():
app = Flask(__name__)
db.init_app(app)
... snip ...
return app
Which creates a scoped_session accessible in db.session.
Apache config is long and complicated but includes the line
WSGIDaemonProcess pixel processes=5 threads=5 display-name='%{GROUP}'
I can post more information if required.
For reference if anyone finds this thread with the same issue, I fixed my problem.
My Flask App factory function had the line app.app_context().push() leftover from the early days when it was based off a Flask tutorial. Unfortunately snipped out of the example code otherwise it might have been spotted by someone. During a restructuring of the project this line was left out and the problem fixed itself. Not sure why or how this line would cause this issue, and only for some but not all requests.

Truncating logging of Post Request in RobotFramework

I am using the Requests library of robot framework to upload files to a server. The file RequestsKeywords.py has a line
logger.info('Post Request using : alias=%s, uri=%s, data=%s, headers=%s, files=%s, allow_redirects=%s '
% (alias, uri, dataStr, headers, files, redir))
This prints out the whole contents of my upload file inside the request in my log file. Now i could get rid of this log by changing the log level however, my goal is to be able to see the log but just truncate it to 80 characters, so I am not browsing through lines of hex values. Any idea how this could be done?
A solution would be to create a wrapper method, that'll temporary disable the logging, and enable it back once completed.
The flow is - get an instance of the RequestsLibrary, call RF's Set Log Level with argument "ERROR" (so at least an error gets through, if needed), call the original keyword, set the log level back to what it was, and return the result.
Here's how it looks like in python:
from robot.libraries.BuiltIn import BuiltIn
def post_request_no_log(*args, **kwargs):
req_lib = BuiltIn().get_library_instance('RequestsLibrary')
current_level = BuiltIn().set_log_level('ERROR')
try:
result = req_lib.post_request(*args, **kwargs)
except Exception as ex:
raise ex
finally:
BuiltIn().set_log_level(current_level)
return result
And the same, in robotframework syntax:
Post Request With No Logging
[Documentation] Runs RequestsLibrary's Post Request, with its logging surpressed
[Arguments] #{args} &{kwargs}
${current level}= Set Log Level ERROR
${result}= Post Request #{args} &{kwargs}
[Return] ${result}
[Teardown] Set Log Level ${current level}
The python's version is bound to be milliseconds faster - no need to parse & match the text in the RF syntax, which on large usage may add up.
Perhaps not the answer you're looking for, but after having looked at the source of the RequestsLibrary I think this is indeed undesirable and should be corrected. It makes sense to have the file contents when running in a debug or trace setting, but not during regular operation.
As I consider this a bug, I'd recommend registering an issue with the GitHub project page or correcting it yourself and providing a pull request. In my opinion the code should be refactored to send the file name under the info setting and the file contents under the trace/debug setting:
logger.info('Post Request using : alias=%s, uri=%s, data=%s, headers=%s, allow_redirects=%s' % ...
logger.trace('Post Request files : files=%s' % ...
In the mean time you have two options. As you correctly said, temporarily reduce the log level settings in Robot Code. If you can't change the script, then using a Robot Framework Listener can help with that. Granted, it would be more work then making the change in the ReqestsLibrary yourself.
An temporary alternative could be to use the RequestLibrary Post, which is deprecated but still present.
If you look at the method in RequestKeywords library, its only calling self. _body_request() at the end. What we ended up doing is writing another keyword that was identical to the original except the part where it called logger.info(). We modified it to log files=%.80s which truncated the file to 80 chars.
def post_request_truncated_logs(
self,
alias,
uri,
data=None,
params=None,
headers=None,
files=None,
allow_redirects=None,
timeout=None):
session = self._cache.switch(alias)
if not files:
data = self._format_data_according_to_header(session, data, headers)
redir = True if allow_redirects is None else allow_redirects
response = self._body_request(
"post",
session,
uri,
data,
params,
files,
headers,
redir,
timeout)
dataStr = self._format_data_to_log_string_according_to_header(data, headers)
logger.info('Post Request using : alias=%s, uri=%s, data=%s, headers=%s, files=%.80s, allow_redirects=%s '
% (alias, uri, dataStr, headers, files, redir))

Flask - url_for automatically escapes '=' to '%3D'

So.. I'm having some issues with Flask's url_for . The code still works.. but when users navigate to a link that was generated by url_for the link looks bad in the address bar.
Namely, I have a decorated view function as follows:
#app.route("/")
#app.route("/page=<int:number")
def index(number=0):
return "Index Page: {}".format(number)
This all works fine except when I try to generate a url for that route. Calling:
url_for("index", number=10)
Yields: domain.tld:80/page%3D10
Is there any way to circumvent this issue? I'd like for '=' to be returned instead of '%3D' when it's built into the route itself.
I only noticed it was doing this when I was testing it in an assert and discovered that the routes were ending up different from what I expected them to be.
At the moment, I have my test case circumvent the issue by using urllib.parse.unquote to fix the url for testing purposes. I could probably just do that for all urls since I won't have any user input to worry about those causing problems.. but it's there for a reason so.... :P
One option you have is to not build the parameter in to the route itself, but use query parameters instead:
from flask import Flask, render_template, request, url_for
app = Flask(__name__)
#app.route("/")
def index():
page = request.args.get('page', 0, type=int)
print(url_for("index", page=10)) # yields /?page=10
return "Index Page: {}".format(page)
app.run(debug=True)
My making use of query parameters for the route, you avoid the issue of Flask encoding the = sign in the route definition.

Avoiding repetition with Flask - but is it too DRY?

Let us assume I serve data to colleagues in-office with a small Flask app, and let us also assume that it is a project I am not explicitly 'paid to do' so I don't have all the time in the world to write code.
It has occurred to me in my experimentation with pet projects at home that instead of decorating every last route with #app.route('/some/local/page') that I can do the following:
from flask import Flask, render_template, url_for, redirect, abort
from collections import OrderedDict
goodURLS = OrderedDict([('/index','Home'), ##can be passed to the template
('/about', 'About'), ##to create the navigation bar
('/foo', 'Foo'),
('/bar', 'Bar'), ##hence the use of OrderedDict
('/eggs', 'Eggs'), ##to have a set order for that navibar
('/spam', 'Spam')])
app = Flask(__name__)
#app.route('/<destination>')
def goThere(destination):
availableRoutes = goodURLS.keys():
if "/" + destination in availableRoutes:
return render_template('/%s.html' % destination, goodURLS=goodURLS)
else:
abort(404)
#app.errorhandler(404)
def notFound(e):
return render_template('/notFound.html'), 404
Now all I need to do is update my one list, and both my navigation bar and route handling function are lock-step.
Alternatively, I've written a method to determine the viable file locations by using os.walk in conjunction with file.endswith('.aGivenFileExtension') to locate every file which I mean to make accessible. The user's request can then be compared against the list this function returns (which obviously changes the serveTheUser() function.
from os import path, walk
def fileFinder(directory, extension=".html"):
"""Returns a list of files with a given file extension at a given path.
By default .html files are returned.
"""
foundFilesList = []
if path.exists(directory):
for p, d, files in walk(directory):
for file in files:
if file.endswith(extension):
foundFilesList.append(file)
return foundFilesList
goodRoutes = fileFinder('./templates/someFolderWithGoodRoutes/')
The question is, Is This Bad?
There are many aspects of Flask I'm just not using (mainly because I haven't needed to know about them yet) - so maybe this is actually limiting, or redundant when compared against a built-in feature of Flask. Does my lack of explicitly decorating each route rob me of a great feature of Flask?
Additionally, is either of these methods more or less safe than the other? I really don't know much about web security - and like I said, right now this is all in-office stuff, the security of my data is assured by our IT professional and there are no incoming requests from outside the office - but in a real-world setting, would either of these be detrimental? In particular, if I am using the backend to os.walk a location on the server's local disk, I'm not asking to have it abused by some ne'er-do-well am I?
EDIT: I've offered this as a bounty, because if it is not a safe or constructive practice I'd like to avoid using it for things that I'd want to like push to Heroku or just in general publicly serve for family, etc. It just seems like decorating every viable route with app.route is a waste of time.
There isn't anything really wrong with your solution, in my opinion. The problem is that with this kind of setup the things you can do are pretty limited.
I'm not sure if you simplified your code to show here, but if all you are doing in your view function is to gather some data and then select one of a few templates to render it then you might as well render the whole thing in a single page and maybe use a Javascript tab control to divide it up in sections on the client.
If each template requires different data, then the logic that obtains and processes the data for each template will have to be in your view function, and that is going to look pretty messy because you'll have a long chain of if statements to handle each template. Between that and separate view functions per template I think the latter will be quicker, even more so if you also consider the maintenance effort.
Update: based on the conversion in the comments I stand by my answer, with some minor reservations.
I think your solution works and has no major problems. I don't see a security risk because you are validating the input that comes from the client before you use it.
You are just using Flask to serve files that can be considered static if you ignore the navigation bar at the top. You should consider compiling the Flask app into a set of static files using an extension like Frozen-Flask, then you just host the compiled files with a regular web server. And when you need to add/remove routes you can modify the Flask app and compile it again.
Another thought is that your Flask app structure will not scale well if you need to add server-side logic. Right now you don't have any logic in the server, everything is handled by jQuery in the browser, so having a single view function works just fine. If at some point you need to add server logic for these pages then you will find that this structure isn't convenient.
I hope this helps.
I assume based on your code that all the routes have a corresponding template file of the same name (destination to destination.html) and that the goodURL menu bar is changed manually. An easier method would be to try to render the template at request and return your 404 page if it doesn't exist.
from jinja2 import TemplateNotFound
from werkzeug import secure_filename
....
#app.route('/<destination>')
def goThere(destination):
destTemplate = secure_filename("%s.html" % destination)
try:
return render_template(destTemplate, goodURLS=goodURLS)
except TemplateNotFound:
abort(404)
#app.errorhandler(404)
def notFound(e):
return render_template('/notFound.html'), 404
This is adapted from the answer to Stackoverflow: How do I create a 404 page?.
Edit: Updated to make use of Werkzeug's secure_filename to clean user input.

Resources