From gabor at szabgab.com Wed May 12 05:00:24 2021 From: gabor at szabgab.com (Gabor Szabo) Date: Wed, 12 May 2021 12:00:24 +0300 Subject: [Flask] Flask-sqlalchemy and testing Message-ID: Hi, I am working on an application using Flask-sqlalchemy and I am not sure how to write tests properly. In all the examples I have seen so far the main flask file has the line db = SQLAlcehmy(app) meaning this is executed when the main file of Flask is loaded. so if the test file has import app that would still happen at compile time. This way I have only one chance to set up a test-database , right before that import-statement. In order to make it easier to use different test database we moved the db = SQLAlcehmy(app) call inside a function decorated with before_first_request. This ensures the code is called for regular use and we can call the before_first_request method for each test. Because we don't have db at load time we also had to move all the class-declarations in this function (because they all inherit from db.Model). The problem is that now all the model classes are scoped to this function. We had to use "global" to make them accessible to the rest of the code. That does not look right. The code can be found here: https://github.com/rnewstead1/workout-app/ (together with the tests) And you can actually see our struggle in this video: https://code-maven.com/workout-app-2 I'd appreciate your insight on how to make this nicer or even just a link to a code-base that has such tests. Gabor -------------- next part -------------- An HTML attachment was scrubbed... URL: From nicolas at lemanchet.fr Wed May 12 05:11:24 2021 From: nicolas at lemanchet.fr (Nicolas Le Manchet) Date: Wed, 12 May 2021 11:11:24 +0200 Subject: [Flask] Flask-sqlalchemy and testing In-Reply-To: References: Message-ID: Hi, You should take a look at application factories (https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/). It's a pattern that allows to have multiple applications configured differently in the same interpreter, which tends to make testing easier. On Wed, May 12, 2021, at 11:00, Gabor Szabo wrote: > Hi, > > I am working on an application using Flask-sqlalchemy and I am not sure > how to write tests properly. > > In all the examples I have seen so far the main flask file has the line > > db = SQLAlcehmy(app) > > meaning this is executed when the main file of Flask is loaded. so if > the test file has > > import app > > that would still happen at compile time. This way I have only one > chance to set up a test-database , right before that import-statement. > > In order to make it easier to use different test database we moved the > db = SQLAlcehmy(app) > call inside a function decorated with before_first_request. > This ensures the code is called for regular use and we can call the > before_first_request method for each test. Because we don't have db at > load time we also had to move all the class-declarations in this > function (because they all inherit from db.Model). > > The problem is that now all the model classes are scoped to this > function. We had to use "global" to make them accessible to the rest of > the code. > > That does not look right. > > The code can be found here: https://github.com/rnewstead1/workout-app/ > (together with the tests) > And you can actually see our struggle in this video: > https://code-maven.com/workout-app-2 > > I'd appreciate your insight on how to make this nicer or even just a > link to a code-base that has such tests. > > Gabor > > > _______________________________________________ > Flask mailing list > Flask at python.org > https://mail.python.org/mailman/listinfo/flask > -- Nicolas Le Manchet From gabor at szabgab.com Thu May 13 09:57:00 2021 From: gabor at szabgab.com (Gabor Szabo) Date: Thu, 13 May 2021 16:57:00 +0300 Subject: [Flask] Flask-sqlalchemy and testing In-Reply-To: References: Message-ID: Nicolas, thanks. That helped a lot and I managed to write a full example that shows this: https://code-maven.com/flask-counter-sqlite-sqlalchemy However I still have an issue. We use yoyo to manage migrations and I wanted to try the automap feature of SQLAlchemy. I could not solve it elegantly just this way: https://code-maven.com/flask-counter-sqlite-sqlalchemy-yoyo * There is a "global" in the model.py * I could not figure out how to create the classes for the table(s) so I added them to a "base" class. * I had to change the query code from Counter.query.all() Any suggestions there? Gabor On Wed, May 12, 2021 at 12:12 PM Nicolas Le Manchet wrote: > Hi, > > You should take a look at application factories ( > https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/). It's > a pattern that allows to have multiple applications configured differently > in the same interpreter, which tends to make testing easier. > > On Wed, May 12, 2021, at 11:00, Gabor Szabo wrote: > > Hi, > > > > I am working on an application using Flask-sqlalchemy and I am not sure > > how to write tests properly. > > > > In all the examples I have seen so far the main flask file has the line > > > > db = SQLAlcehmy(app) > > > > meaning this is executed when the main file of Flask is loaded. so if > > the test file has > > > > import app > > > > that would still happen at compile time. This way I have only one > > chance to set up a test-database , right before that import-statement. > > > > In order to make it easier to use different test database we moved the > > db = SQLAlcehmy(app) > > call inside a function decorated with before_first_request. > > This ensures the code is called for regular use and we can call the > > before_first_request method for each test. Because we don't have db at > > load time we also had to move all the class-declarations in this > > function (because they all inherit from db.Model). > > > > The problem is that now all the model classes are scoped to this > > function. We had to use "global" to make them accessible to the rest of > > the code. > > > > That does not look right. > > > > The code can be found here: https://github.com/rnewstead1/workout-app/ > > (together with the tests) > > And you can actually see our struggle in this video: > > https://code-maven.com/workout-app-2 > > > > I'd appreciate your insight on how to make this nicer or even just a > > link to a code-base that has such tests. > > > > Gabor > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From gabor at szabgab.com Fri May 14 01:03:28 2021 From: gabor at szabgab.com (Gabor Szabo) Date: Fri, 14 May 2021 08:03:28 +0300 Subject: [Flask] Flask-sqlalchemy and testing In-Reply-To: References: Message-ID: I've solved most of the issues in the second example though I still have to write db.session.query(Counter).all() instead of the much nicer Counter.query.all() but maybe one day I'll figure that out too. Gabor On Thu, May 13, 2021 at 4:57 PM Gabor Szabo wrote: > Nicolas, thanks. That helped a lot and I managed to write a full example > that shows this: https://code-maven.com/flask-counter-sqlite-sqlalchemy > > > However I still have an issue. We use yoyo to manage migrations and I > wanted to try the automap feature of SQLAlchemy. I could not solve it > elegantly just this way: > https://code-maven.com/flask-counter-sqlite-sqlalchemy-yoyo > > * There is a "global" in the model.py > * I could not figure out how to create the classes for the table(s) so I > added them to a "base" class. > * I had to change the query code from Counter.query.all() > > Any suggestions there? > > Gabor > > On Wed, May 12, 2021 at 12:12 PM Nicolas Le Manchet > wrote: > >> Hi, >> >> You should take a look at application factories ( >> https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/). It's >> a pattern that allows to have multiple applications configured differently >> in the same interpreter, which tends to make testing easier. >> >> On Wed, May 12, 2021, at 11:00, Gabor Szabo wrote: >> > Hi, >> > >> > I am working on an application using Flask-sqlalchemy and I am not sure >> > how to write tests properly. >> > >> > In all the examples I have seen so far the main flask file has the line >> > >> > db = SQLAlcehmy(app) >> > >> > meaning this is executed when the main file of Flask is loaded. so if >> > the test file has >> > >> > import app >> > >> > that would still happen at compile time. This way I have only one >> > chance to set up a test-database , right before that import-statement. >> > >> > In order to make it easier to use different test database we moved the >> > db = SQLAlcehmy(app) >> > call inside a function decorated with before_first_request. >> > This ensures the code is called for regular use and we can call the >> > before_first_request method for each test. Because we don't have db at >> > load time we also had to move all the class-declarations in this >> > function (because they all inherit from db.Model). >> > >> > The problem is that now all the model classes are scoped to this >> > function. We had to use "global" to make them accessible to the rest of >> > the code. >> > >> > That does not look right. >> > >> > The code can be found here: https://github.com/rnewstead1/workout-app/ >> > (together with the tests) >> > And you can actually see our struggle in this video: >> > https://code-maven.com/workout-app-2 >> > >> > I'd appreciate your insight on how to make this nicer or even just a >> > link to a code-base that has such tests. >> > >> > Gabor >> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From samuel.freeman at gmail.com Sun May 16 06:18:40 2021 From: samuel.freeman at gmail.com (Samuel Freeman) Date: Sun, 16 May 2021 11:18:40 +0100 Subject: [Flask] Flask-sqlalchemy and testing In-Reply-To: References: Message-ID: Hello, Thanks both for the above, helps towards my understanding of how some of these things work : ) with apologies for going a bit off topic, it was Gabor's final comment that sparked my reply - I also hope to oneday figure out how/why the different patterns work I have never found the Counter.query.all() way of calling to work in my apps, instead relying on the db.session.query(Counter).all() way... I made the following util function to help me be less annoyed about it, but it seems not very pythonic to hide the details in this way: ``` def db_q(what): return db.session.query(what) ``` >>> db_q(Counter).all() I also struggle to get tests working most of the time, again because I am yet to understand some of the patterns involved. Samuel On Fri, 14 May 2021 at 06:04, Gabor Szabo wrote: > I've solved most of the issues in the second example though I still have > to write > db.session.query(Counter).all() > instead of the much nicer > Counter.query.all() > but maybe one day I'll figure that out too. > > Gabor > > On Thu, May 13, 2021 at 4:57 PM Gabor Szabo wrote: > >> Nicolas, thanks. That helped a lot and I managed to write a full example >> that shows this: https://code-maven.com/flask-counter-sqlite-sqlalchemy >> >> >> However I still have an issue. We use yoyo to manage migrations and I >> wanted to try the automap feature of SQLAlchemy. I could not solve it >> elegantly just this way: >> https://code-maven.com/flask-counter-sqlite-sqlalchemy-yoyo >> >> * There is a "global" in the model.py >> * I could not figure out how to create the classes for the table(s) so I >> added them to a "base" class. >> * I had to change the query code from Counter.query.all() >> >> Any suggestions there? >> >> Gabor >> >> On Wed, May 12, 2021 at 12:12 PM Nicolas Le Manchet >> wrote: >> >>> Hi, >>> >>> You should take a look at application factories ( >>> https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/). >>> It's a pattern that allows to have multiple applications configured >>> differently in the same interpreter, which tends to make testing easier. >>> >>> On Wed, May 12, 2021, at 11:00, Gabor Szabo wrote: >>> > Hi, >>> > >>> > I am working on an application using Flask-sqlalchemy and I am not >>> sure >>> > how to write tests properly. >>> > >>> > In all the examples I have seen so far the main flask file has the line >>> > >>> > db = SQLAlcehmy(app) >>> > >>> > meaning this is executed when the main file of Flask is loaded. so if >>> > the test file has >>> > >>> > import app >>> > >>> > that would still happen at compile time. This way I have only one >>> > chance to set up a test-database , right before that import-statement. >>> > >>> > In order to make it easier to use different test database we moved the >>> > db = SQLAlcehmy(app) >>> > call inside a function decorated with before_first_request. >>> > This ensures the code is called for regular use and we can call the >>> > before_first_request method for each test. Because we don't have db at >>> > load time we also had to move all the class-declarations in this >>> > function (because they all inherit from db.Model). >>> > >>> > The problem is that now all the model classes are scoped to this >>> > function. We had to use "global" to make them accessible to the rest >>> of >>> > the code. >>> > >>> > That does not look right. >>> > >>> > The code can be found here: https://github.com/rnewstead1/workout-app/ >>> > (together with the tests) >>> > And you can actually see our struggle in this video: >>> > https://code-maven.com/workout-app-2 >>> > >>> > I'd appreciate your insight on how to make this nicer or even just a >>> > link to a code-base that has such tests. >>> > >>> > Gabor >>> >>> _______________________________________________ > Flask mailing list > Flask at python.org > https://mail.python.org/mailman/listinfo/flask > -------------- next part -------------- An HTML attachment was scrubbed... URL: From gabor at szabgab.com Mon May 17 01:20:30 2021 From: gabor at szabgab.com (Gabor Szabo) Date: Mon, 17 May 2021 08:20:30 +0300 Subject: [Flask] Flask-sqlalchemy and testing In-Reply-To: References: Message-ID: On Sun, May 16, 2021 at 1:19 PM Samuel Freeman wrote: > Hello, > Thanks both for the above, helps towards my understanding of how some of > these things work : ) > > with apologies for going a bit off topic, it was Gabor's final comment > that sparked my reply - I also hope to oneday figure out how/why the > different patterns work > > I have never found the > Counter.query.all() > way of calling to work in my apps, instead relying on the > db.session.query(Counter).all() > way... > > As far as I can tell the former is provided by flask-sqlalchemy, the latter is plain SQLAlchemy. Gabor -------------- next part -------------- An HTML attachment was scrubbed... URL: From chatzistdimi at gmail.com Mon May 24 03:01:55 2021 From: chatzistdimi at gmail.com (=?UTF-8?B?zpTOrs68zrfPhM+BzrEgzqfOsc+EzrbOt8+Dz4TOsc68zrHPhM6vzr/PhQ==?=) Date: Mon, 24 May 2021 10:01:55 +0300 Subject: [Flask] help Message-ID: Hello, I'm currently working on your Flask project not as contributor but by performing software engineering tasks on Flask for my uni course at DMST AUEB ?. This week, we are dealing with software engineering management. I have to evaluate the project's execution management. For that reason, I search for: -software acquisition & Supplier contract management - implementation of measurement process - reporting among other things. I'm lost in documentation so if you could help me and write me a few words about the contracts if there are any, or about the measurement process or when you release reports, I would really appreciate it!! Thanks for your time !! Dimitra Chatzistamatiou. -------------- next part -------------- An HTML attachment was scrubbed... URL: From dlenski at gmail.com Mon May 24 18:27:01 2021 From: dlenski at gmail.com (Daniel Lenski) Date: Mon, 24 May 2021 15:27:01 -0700 Subject: [Flask] HTTP/1.1 connection re-use broken in Flask/werkzeug development server? Message-ID: Hi, I'm one of the developers of the OpenConnect multi-protocol VPN client. We've been using Flask to emulate the HTTP-based authentication front-ends of VPN servers [1], and it's been working really well for this purpose. I recently discovered that the Flask development server appears to be unable to correctly handle HTTP/1.1 connection reuse [2]. When a connection to the development server is reused for multiple requests, the *body* of one request will be misinterpreted as a new HTTP/1.1 request line. Here's a complete example, tested against Python 3.6.9, Werkzeug (2.0.1), and Flask (2.0.1): from flask import Flask, abort from werkzeug.serving import WSGIRequestHandler WSGIRequestHandler.protocol_version = "HTTP/1.1" # Create Flask app app = Flask(__name__) app.config.update(DEBUG=True, PORT=int(port)) # Add a route that accepts POST @app.route('/', methods=('POST',)) def post_slash(): assert 'foo' in request.form return '', 200 # Run it app.run(host='localhost', port='8080', debug=True) Demonstration of incorrect parsing of multiple requests by this server, with curl connection reuse: curl -v -X POST http://localhost:8080 -d foo=bar -: -v -X POST http://localhost:8080 -d foo=baz Server log shows: 127.0.0.1 - - [24/May/2021 12:28:22] "POST / HTTP/1.1" 200 - 127.0.0.1 - - [24/May/2021 12:28:22] "foo=barPOST / HTTP/1.1" 405 - 127.0.0.1 - - [24/May/2021 12:28:22] code 400, message Bad request syntax ('foo=baz') 127.0.0.1 - - [24/May/2021 12:28:22] "None / HTTP/0.9" HTTPStatus.BAD_REQUEST - As mentioned above, the server is evidently misinterpreting the body of the first request (b"foo=bar" on the wire) as the beginning of the second request. That's *despite* the fact that the server has clearly *read* the body and parsed it into the request.form object (otherwise the assertion would have failed). It appears that the server is not correctly advancing its position in the connection stream after reading the body, and thus restarting at the wrong position when awaiting subsequent requests. I also tested with HTTPS (by setting a valid ssl_context in app.run) and got the same results. If the connection is not reused by the client (e.g. two separate curl commands) then the server handles the second request correctly. It also succeeds if protocol_version = "HTTP/1.1" is removed from the server configuration, thus forcing the client *not* to try to reuse connections, because cause the server identifies itself as HTTP/1.0, which doesn't allow connection reuse. Is this a known issue in Flask or Werkzeug? For the time being, it appears the only workaround is not to reuse HTTP/1.1 connections at all? any easy fix/patch? Thanks, Dan Lenski [1] See fake-*-server.py in https://gitlab.com/openconnect/openconnect/tree/master/tests) [2] https://en.wikipedia.org/wiki/HTTP_persistent_connection#HTTP_1.1 -------------- next part -------------- An HTML attachment was scrubbed... URL: From dlenski at gmail.com Mon May 24 20:36:42 2021 From: dlenski at gmail.com (Daniel Lenski) Date: Mon, 24 May 2021 17:36:42 -0700 Subject: [Flask] HTTP/1.1 connection re-use broken in Flask/werkzeug development server? In-Reply-To: References: Message-ID: On Mon, May 24, 2021 at 3:27 PM Daniel Lenski wrote: > I recently discovered that the Flask development server appears to be unable to correctly handle HTTP/1.1 connection reuse [2]. When a connection to the development server is reused for multiple requests, the *body* of one request will be misinterpreted as a new HTTP/1.1 request line. In attempting to come up with a minimal example of this problem, I both overstated the scope, as well as sent a non-functional examples. Sorry for anyone who read it already. Here's one that I've just tested, which I believe clarifies the scope of the problem: from flask import Flask, abort, request from werkzeug.serving import WSGIRequestHandler WSGIRequestHandler.protocol_version = "HTTP/1.1" app = Flask(__name__) # Accepts POST and consumes body @app.route('/consume_body', methods=('POST',)) def consume_body(): n = len(request.form) return 'Form contained %d fields\n' % n, 200 # Accepts POST but doesn't consume body @app.route('/ignore_body', methods=('POST',)) def ignore_body(): return 'Ignored body', 200 # Accepts POST but fails before consuming body @app.route('/fail_first', methods=('POST',)) def fail_first(): abort(500) n = len(request.form) return 'Form contained %d fields\n' % n, 200 # Run it app.run(host='localhost', port='8080', debug=True) If I reuse an HTTP/1.1 connection to issue multiple requests to a handler where the request body is consumed, it WORKS FINE: curl -X POST http://localhost:8080/consume_body -d foo=bar -: -X POST http://localhost:8080/consume_body -d 'a=b&x=y' Form contained 1 fields Form contained 2 fields server log shows: 127.0.0.1 - - [24/May/2021 15:59:26] "POST /consume_body HTTP/1.1" 200 - 127.0.0.1 - - [24/May/2021 15:59:26] "POST /consume_body HTTP/1.1" 200 - However, if I reuse an HTTP/1.1 connection to issue multiple requests to a route where the request body ISN'T consumed, the body of earlier requests is misinterpreted as the beginning of a subsequent request: curl -X POST http://localhost:8080/consume_body -d foo=bar -: -X POST http://localhost:8080/consume_body -d 'a=b&x=y' server log shows: 127.0.0.1 - - [24/May/2021 16:01:00] "POST /fail_first HTTP/1.1" 500 - 127.0.0.1 - - [24/May/2021 16:01:00] "foo=barPOST /consume_body HTTP/1.1" 405 - 127.0.0.1 - - [24/May/2021 16:01:00] code 400, message Bad request syntax ('a=b&x=y') 127.0.0.1 - - [24/May/2021 16:01:00] "None /consume_body HTTP/0.9" HTTPStatus.BAD_REQUEST - What I now understand is going on here: the development server isn't consuming the request body until explicitly used referenced. Which happens in werkzeug.wrappers.Request.get_data() in https://github.com/pallets/werkzeug/blob/main/src/werkzeug/wrappers/request.py#L367-L373 This means that if the request body isn't consumed, then the request body is never read and the stream is at the wrong place for the next request. This could happen due to a mistake in the code, due to an error in the handler, or due to the fact that a request body wasn't expected (e.g. a Content-Length header and body added teo a GET request). One workaround is to wrap EVERY handler in 'try... finally: request.data' to ensure that the request body is ALWAYS read before returning to the server. However, that seems like a fairly large burden to place on the user. Ideas I had to make this behavior less-surprising? 1. Ensure that werkzeug's WSGIRequestHandler.run_wsgi forcibly closes the connection if the input stream hasn't been fully read after the WSGI handler runs (https://github.com/pallets/werkzeug/blob/main/src/werkzeug/serving.py#L275-L280) 2. Ensure that werkzeug's WSGIRequestHandler.run_wsgi reads to the end of the input stream if it hasn't already been read after the WSGI handler runs. Problems: - Deciding that the input stream "has been fully read" is only easy in the cases where there's an explicit 'Content-Length' header in the *request*. - "Fully reading" the stream might take a lot of time and memory if the request 'Content-Length' is large. - "Fully reading" the stream could take infinite time and memory with a malicious/infinite chunked Transfer-Encoding. - In the case of HTTP CONNECT method (which turns the HTTP connection back into a standard 2-way TCP socket), the *only* solution is to close the connection. My preferred solution would be to read to the end of the input stream if it 'Content-Length' is set and not too large (perhaps following the MAX_CONTENT_LENGTH configuration as in werkzeug.wrappers.Request; https://github.com/pallets/flask/blob/main/src/flask)/wrappers.py#L57) and to simply close the connection otherwise. I'd be interested in hearing any input before I try to code this up? Thanks, Dan