- Welcome | Flask (A Python Microframework)
- 基於 Werkzeug 及 Jinja2 的 Python-based microframework;相容於 WSGI 1.0
- 內建 development server 及 debugger,提供了 unit testing 的支援。
- RESTful request dispatching -- 指 routing。
- 支援 cookies (client side session)
- What Flask is, What Flask is Not - Design Decisions in Flask — Flask 1.0.2 documentation Flask 永遠不會有 database layer、form library 等,它只是橋接了 Werkzeug (WSGI application) 及 Jinja2 (templating),其他的都是 extension,因為大家都有不同的需求。
- Deploy and scale Python & Django in the cloud | Heroku Choice of frameworks 提到 MVC web apps with Django, lightweight APIs with Flask, flexible apps with Pyramid, evented apps with Twisted and headless worker apps 似乎 Django、Flask、Pyramid, Twisted 有各自不同的定位,顯然 Flask 很適合用來寫 API。
- Installation — Flask 1.0.2 documentation Werkzeug 實作了 WSGI,做為 application 與 web server 間的標準 Python interface。
- Welcome | Flask (A Python Microframework) 基於 Werkzeug 及 Jinja2 的 Python-based microframework。
- Welcome | Flask (A Python Microframework) 基於 Werkzeug 及 Jinja2 的 Python-based microframework;拿 Jinja2 當 template engine。
- Installation — Flask 1.0.2 documentation Jinja 是一種 template language,用來畫 (render) web pages;Jinja 會帶出 MarkupSafe -- 將 untrusted input 做 escape,避免 injection 攻擊。
hello.py
:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
$ FLASK_APP=hello.py FLASK_DEBUG=1 flask run
$ curl http://127.0.0.1:5000/
Hello, World!
若採用 package 的話 (hello/__init__.py
),Flask 1.0 前要用:
$ FLASK_APP=hello/__init__.py FLASK_DEBUG=1 flask run
或
$ FLASK_APP=hello FLASK_DEBUG=1 python -m flask run
參考資料:
- Welcome to Flask — Flask 1.0.2 documentation #ril
- Quickstart — Flask 1.0.2 documentation #ril
- Tutorial — Flask 1.0.2 documentation #ril
- 用 virtualenv 安裝
Flask
套件。 - 跟著 Qucikstart 做過一遍。
參考資料:
- Quickstart — Flask Documentation http://flask.pocoo.org/docs/latest/quickstart/ #ril
- realpython/flask-skeleton: Real Python Flask Starter Project https://github.com/realpython/flask-skeleton #ril
- Quickstart — Flask Documentation http://flask.pocoo.org/docs/quickstart/ #ril
- http://flask.pocoo.org/docs/latest/ 學習路徑: 安裝 > Quickstart > Tutorial > Patterns
- 為什麼
app = Flask(__name__)
會是這種用法?? 傳入的__name__
說是 "the name of the application package" ... - 怎麼做 logging? => https://gist.github.com/ibeex/3257877 用
app.logger.xxx()
- 什麼是 WSGI application?? 相關的工具的支援...
- Tutorial — Flask Documentation http://flask.pocoo.org/docs/latest/tutorial/ 完整的範例 #ril
- Patterns for Flask — Flask 1.0.2 documentation #ril
- 什麼是
path
converter?? 可以用在 rule 中間嗎? 又怎麼沒有bool
converter??
參考資料:
-
Welcome | Flask (A Python Microframework) 提到 RESTful request dispatching 就是 routing,但跟 RESTful 什麼關係??
-
python - Can Flask have optional URL parameters? - Stack Overflow 宣告兩個 route,搭配 function 的 default value (可讀性比官方文件裡
@app.route('/users/', defaults={'page': 1})
的用法更高);若一個 function 有多個 route,那url_for()
怎麼用??@app.route('/<user_id>') @app.route('/<user_id>/<username>') def show(user_id, username='Anonymous'): return user_id + ':' + username
-
Context Locals - Quickstart — Flask 1.0.2 documentation
-
Insider Information: If you want to understand how that works and how you can implement TESTS with context locals, read this section, otherwise just skip it. 跟測試有關??
-
Certain objects in Flask are GLOBAL objects, but not of the usual kind. These objects are actually PROXIES to objects that are LOCAL TO A SPECIFIC CONTEXT. What a mouthful. But that is actually quite easy to understand.
-
Imagine the context being the HANDLING THREAD. A request comes in and the web server decides to spawn a new thread (or something else, the underlying object is capable of dealing with concurrency systems other than threads). When Flask starts its internal request handling it figures out that the current thread is the active context and BINDS the current application and the WSGI environments to that context (thread). It does that in an intelligent way so that one application can INVOKE ANOTHER APPLICATION?? without breaking.
-
So what does this mean to you? Basically you can completely ignore that this is the case unless you are doing something like UNIT TESTING. You will notice that code which depends on a request object will suddenly break because there is no request object. The solution is creating a request object yourself and binding it to the context. The easiest solution for unit testing is to use the
test_request_context()
context manager. In combination with thewith
statement it will bind a test request so that you can interact with it. Here is an example:from flask import request with app.test_request_context('/hello', method='POST'): # now you can do something with the request until the # end of the with block, such as basic assertions: assert request.path == '/hello' assert request.method == 'POST'
這跟
Flask.test_client()
是什麼關係?? 好像可以不用從 routing 這一層測試? 不過話說回來,離開 routing 這一層還跟 Flask 有相依,也是個問題... -
The other possibility is passing a whole WSGI environment to the
request_context()
method: 直接模擬環境變數??from flask import request with app.request_context(environ): assert request.method == 'POST'
-
-
Quickstart — Flask 1.0.2 documentation
- For web applications it’s crucial to react to the data a client sends to the server. In Flask this information is provided by the GLOBAL
request
object. If you have some experience with Python you might be wondering how that object can be global and how Flask manages to still be THREADSAFE. The answer is CONTEXT LOCALS:
The Request Object
-
The request object is documented in the API section and we will not cover it here in detail (see
Request
). Here is a broad overview of some of the most common operations. First of all you have to import it from theflask
module:from flask import request
-
The current request method is available by using the
method
attribute. To access form data (data transmitted in a POST or PUT request) you can use theform
attribute. Here is a full example of the two attributes mentioned above:@app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) # 裡面應該會實現 Redirect After Post else: error = 'Invalid username/password' # the code below is executed if the request method # was GET or the credentials were invalid return render_template('login.html', error=error)
-
What happens if the key does not exist in the
form
attribute? In that case a specialKeyError
is raised. You can catch it like a standardKeyError
but if you don’t do that, a HTTP 400 Bad Request error page is shown instead. So for many situations you DON’T have to deal with that problem. -
To access parameters submitted in the URL (
?key=value
) you can use theargs
attribute:searchword = request.args.get('key', '')
-
We recommend accessing URL parameters with
get
or by catching theKeyError
because users might change the URL and presenting them a 400 bad request page in that case is NOT USER FRIENDLY.還是得看情況吧,如果必要的參數沒拿到,又沒有合理的預設值,回 400 也是很合理的?
-
For a full list of methods and attributes of the request object, head over to the
Request
documentation.
- For web applications it’s crucial to react to the data a client sends to the server. In Flask this information is provided by the GLOBAL
-
The Request Context — Flask 1.0.2 documentation #ril
-
The request context keeps track of the REQUEST-LEVEL data during a request. Rather than passing the request object to each function that runs during a request, the
request
andsession
PROXIES are accessed instead. -
This is similar to the The Application Context, which keeps track of the APPLICATION-LEVEL data independent of a request. A corresponding application context is PUSHED when a request context is pushed.
關於 push/pop, context 要先看過下面 How the Context Works 才會懂,簡單的講
request
、session
這些 proxy object 都參照 request/application context stack 最上方的 context 來決定 current request/application,所以 push/pop 的動作很關鍵。
Purpose of the Context
-
When the
Flask
application handles a request, it creates aRequest
object based on the environment it received from the WSGI server. Because a WORKER (thread, process, or COROUTINE depending on the server) handles only one request at a time, the request data can be considered GLOBAL TO THAT WORKER during that request. Flask uses the term CONTEXT LOCAL for this.原來 worker 是這麼抽象,背後可能有不同的實作方式,但 worker 同時間只會面對一個 reqeust,這一點是肯定的。
-
Flask automatically pushes a request context when handling a request. View functions, error handlers, and other functions that run during a request will have access to the request proxy, which POINTS TO the request object for the current request.
Lifetime of the Context
- When a Flask application begins handling a request, it pushes a request context, which also pushes an The Application Context. When the request ends it pops the request context then the application context.
- The context is unique to each thread (or other worker type). request cannot be passed to another thread, the other thread will have a different CONTEXT STACK and will not know about the request the PARENT THREAD?? was pointing to.
- Context locals are implemented in Werkzeug. See Context Locals for more information on how this works internally. 已經有 context,為何又要強調 local??
...
How the Context Works
-
The
Flask.wsgi_app()
method is called to handle each request. It manages the contexts during the request. Internally, the request and application contexts work as STACKS,_request_ctx_stack
and_app_ctx_stack
. When contexts are PUSHED onto the stack, the proxies that depend on them are available and point at information from the TOP CONTEXT on the stack.難怪會有 push/pop 的說法,而且 request context 跟 application context 是兩個不同的 stack。
-
When the request starts, a
RequestContext
is created and pushed, which creates and pushes anAppContext
FIRST if a context for that application is not already the top context. While these contexts are pushed, thecurrent_app
,g
,request
, andsession
proxies are available to the ORIGINAL THREAD handling the request. -
After the request is dispatched and a response is generated and sent, the request context is popped, which then pops the application context. Immediately before they are popped, the
teardown_request()
andteardown_appcontext()
functions are are executed. These execute EVEN IF an unhandled exception occurred during dispatch. 這 2 個 callback 都在flask.Flask
上。
-
-
flask.request
- API — Flask 1.0.2 documentation-
To access incoming request data, you can use the GLOBAL
request
object. Flask parses incoming request data for you and gives you access to it through that global object. Internally Flask makes sure that you always get the correct data for the ACTIVE THREAD if you are in a multithreaded environment.This is a PROXY (
werkzeug.local.LocalProxy
). See Notes On Proxies for more information. -
The request object is an instance of a
Request
subclass and provides all of the attributes Werkzeug defines. This just shows a quick overview of the most important ones.
-
-
class flask.Request
- API — Flask 1.0.2 documentation #ril-
The request object used by default in Flask. Remembers the MATCHED ENDPOINT and VIEW ARGUMENTS ??.
It is what ends up as
flask.request
. If you want to replace the request object used you can subclass this and setFlask.request_class
to your subclass. -
The request object is a
Request
subclass and provides all of the attributes Werkzeug defines PLUS A FEW Flask SPECIFIC ONES.werkzeug.wrappers.Request
繼承自werkzeug.wrappers.BaseRequest
。
environ
- The underlying WSGI environment.
path
,full_path
,script_root
,url
,base_url
,url_root
-
Provides different ways to look at the current IRI. Imagine your application is listening on the following application root:
http://www.example.com/myapplication
And a user requests the following URI:
http://www.example.com/myapplication/%CF%80/page.html?x=y
In this case the values of the above mentioned attributes would be the following:
path=u'/π/page.html' full_path=u'/π/page.html?x=y' script_root=u'/myapplication' base_url=u'http://www.example.com/myapplication/π/page.html' url=u'http://www.example.com/myapplication/π/page.html?x=y' url_root=u'http://www.example.com/myapplication/'
args
-
The parsed URL parameters (the part in the URL after the question mark).
也就是 query string。
-
By default an
ImmutableMultiDict
is returned from this function. This can be changed by settingparameter_storage_class
to a different type. This might be necessary if the ORDER of the form data is important.
content_encoding
- The
Content-Encoding
entity-header field is used as a modifier to the media-type. When present, its value indicates what additional content codings have been applied to the entity-body, and thus what decoding mechanisms must be applied in order to obtain the media-type referenced by theContent-Type
header field. ??
content_length
- The
Content-Length
entity-header field indicates the size of the entity-body in bytes or, in the case of theHEAD
method, the size of the entity-body that would have been sent had the request been a GET. ??
content_type
- The
Content-Type
entity-header field indicates the media type of the entity-body sent to the recipient or, in the case of the HEAD method, the media type that would have been sent had the request been a GET.
cookies
- A
dict
with the contents of all cookies transmitted with the request.
data
-
Contains the incoming request data as string in case it came with a mimetype Werkzeug DOES NOT HANDLE.
也就是說,如果 Werkzeug 認得 content type,
data
就會是空的? 這聽起來很不直覺,得從實作細節看起;BeseRequest.data
:def data(self): if self.disable_data_descriptor: raise AttributeError("data descriptor is disabled") return self.get_data(parse_form_data=True)
對照下面
get_data()
的實作,就算cache
預設為True
,但因為parse_form_data=True
先讀取並解析了 input stream,所以rv = self.stream.read()
就讀不到東西了。也因此
data
通常都拿不到東西,若真的想拿原始的 data,得在解析 form data 之前先呼叫get_data()
並把資料快取起來,之後解析 form data 時才有資料。簡單做個時驗,看request.form
對request.get_data()
及request.data
會造成什麼影響:@app.route('/', methods=['POST']) def post(): logging.info('request.get_data() = %r', request.get_data()) logging.info('request.data = %r', request.data) logging.info('request.form = %r', request.form)
用
curl --data 'key=value' http://...
會得到:root - request.get_data() = 'key=val' root - request.data = 'key=val' root - request.form = ImmutableMultiDict([('key', u'val')])
把
request.form
放到最前面:root - request.form = ImmutableMultiDict([('key', u'val')]) root - request.get_data() = '' root - request.data = ''
把
request.data
放到最前面,結果最詭異: (看過實作細節才能理解)root - request.data = '' root - request.get_data() = '' root - request.form = ImmutableMultiDict([('key', u'val')])
-
The form parameters. By default an
ImmutableMultiDict
is returned from this function. This can be changed by settingparameter_storage_class
to a different type. This might be necessary if the order of the form data is important. -
Please keep in mind that file uploads will not end up here, but instead in the
files
attribute.
get_data(cache=True, as_text=False, parse_form_data=False)
-
This reads the BUFFERED INCOMING DATA from the client into one BYTESTRING. By default this is CACHED but that behavior can be changed by setting
cache
toFalse
.根據
BaseRequest.get_data()
的實作,從 input stream 讀進來的值會快取在_cached_data
裡,之後存取BaseRequest.form
時會[觸發_load_form_data()](https://github.com/pallets/werkzeug/blob/0.15.x/src/werkzeug/wrappers/base_request.py#L479),就會[間接從
_get_stream_for_parsing()](https://github.com/pallets/werkzeug/blob/0.15.x/src/werkzeug/wrappers/base_request.py#L318) 取得
_cached_data`。def get_data(self, cache=True, as_text=False, parse_form_data=False): rv = getattr(self, "_cached_data", None) if rv is None: if parse_form_data: self._load_form_data() # 除非有 _cached_data,否則會從 self.stream 讀資料 rv = self.stream.read() # 如果做了 _load_form_data(),stream 裡就沒資料可讀了 if cache: self._cached_data = rv # 快取的資料也會是空的 if as_text: rv = rv.decode(self.charset, self.encoding_errors) return rv def _get_stream_for_parsing(self): cached_data = getattr(self, "_cached_data", None) if cached_data is not None: return BytesIO(cached_data) return self.stream
-
Usually it’s a bad idea to call this method without checking the content length first as a client could send dozens of megabytes or more to cause memory problems on the server.
聽起來是前端 web server 的責任 ??
-
Note that if the FORM DATA WAS ALREADY PARSED this method will not return anything as form data parsing does not cache the data like this method does.
To implicitly invoke form data parsing function set
parse_form_data
toTrue
. When this is done the return value of this method will be an EMPTY STRING if the form parser handles the data. This generally is not necessary as if the whole data is cached (which is the default) the form parser will used the cached data to parse the form data. Please be generally aware of checking the content length first in any case before calling this method to avoid exhausting server memory. -
If
as_text
is set toTrue
the return value will be a decoded UNICODE string.
get_json(force=False, silent=False, cache=True)
-
Parse and return the data as JSON. If the mimetype does not indicate JSON (
application/json
, seeis_json()
), this returnsNone
unlessforce
is true. If parsing fails,on_json_loading_failed()
is called and its return value is used as the return value. -
Parameters:
force
– Ignore the mimetype and always try to parse JSON.silent
– Silence parsing errors and returnNone
instead.cache
– Store the parsed JSON to return for subsequent calls.
headers
- The headers from the WSGI environ as immutable
EnvironHeaders
.
json
-
This will contain the parsed JSON data if the mimetype indicates JSON (
application/json
, seeis_json()
), otherwise it will beNone
.這很依賴 client 是否有帶正確的 content type,如果要無條件視為 JSON 則要改用
get_json(force=True)
。
mimetype
- Like content_type, but without PARAMETERS (eg, without charset, type etc.) and always LOWERCASE. For example if the content type is
text/HTML; charset=utf-8
the mimetype would be'text/html'
.
mimetype_params
- The mimetype parameters as dict. For example if the content type is
text/html; charset=utf-8
the params would be{'charset': 'utf-8'}
.
stream
-
If the incoming form data was not encoded with a known mimetype the data is stored unmodified in this stream for consumption. Most of the time it is a better idea to use
data
which will give you that data as a string. THE STREAM ONLY RETURNS THE DATA ONCE. -
Unlike
input_stream
this stream is properly guarded that you can’t accidentally read past the length of the input. Werkzeug will internally always refer to this stream to read data which makes it possible to wrap this object with a stream that does filtering. ??
values
-
A
werkzeug.datastructures.CombinedMultiDict
that combinesargs
andform
.用在資料從哪裡來不重要時。
-
- View function 直接 return,不傳回任何東西,就是單純的 200 OK 嗎? 還是用
abort(200)
比較好?- 單純的
return
(同return None
) 會引發ValueError: View function did not return a response
的錯誤。 abort(200)
會引發LookupError: no exception for 200
的錯誤。- 目前似乎只能回
return ''
??
- 單純的
- Flask 裡怎麼先回應一些訊息,之後再透過 callback URL 再回一些?? 就算能把工作交出去,那之後結果要怎麼更新到前端??
- http://stackoverflow.com/questions/12317667/ 用 Celery 或 Twisted,但需要搭配 external worker process;這會不會太小題大作了??
- multithreading - How do I run a long-running job in the background in Python - Stack Overflow http://stackoverflow.com/questions/34321986/ "the route will return a url (using the guid) that the user can use to check progress." 這做法似乎不錯??
- Celery Based Background Tasks — Flask Documentation http://flask.pocoo.org/docs/latest/patterns/celery/ 官方的 pattern 也提到 Celery #ril
- Redirects and Errors - Quickstart — Flask 1.0.2 documentation #ril
- The Request Context — Flask 1.0.2 documentation
- Because the contexts are stacks, other contexts may be pushed to CHANGE THE PROXIES DURING A REQUEST. While this is not a common pattern, it can be used in advanced applications to, for example, do INTERNAL REDIRECTS or chain different applications together. 怎麼達成 internal redirect??
- python - Flask: redirect to same page after form submission - Stack Overflow #ril
- Welcome | Flask (A Python Microframework) 支援 cookies (client side session)
- Installation — Flask 1.0.2 documentation ItsDangerous 可以做數位簽章 (signing),Flask 用它來確保 session cookie 沒有被動過手腳 (integrity)。
- Sessions | Flask (A Python Microframework) #ril
- Sessions - Quickstart — Flask 1.0.2 documentation #ril
- class flask.session - API — Flask 1.0.2 documentation #ril
- http://flask.pocoo.org/docs/1.0/quickstart/ #sessions
- 實作 Flask Session Interface 將Session 資料存入資料庫 — 使用 FlaskSession套件 使用 Flask-Session 後,cookie 裡只剩 session ID #ril
- 可能的方案有 Flask-RESTful、Flask-RESTPlus、Connexion、Flasgger 等。
- 提供 OAuth 驗證、產生 API 文件等,都是標準配備;目前好像只有 Connexion 內建支援 OAuth ??
- Welcome | Flask (A Python Microframework) 提到內建 development server。
- Installation — Flask 1.0.2 documentation Watchdog 提供更好的 reloader 讓 development server 使用。
- Welcome | Flask (A Python Microframework) 提到內建 debugger。
- Environment Variables From dotenv - Command Line Interface — Flask 1.0.2 documentation
.flaskenv
放 public variables (例如FLASK_APP
),而.env
可以放 private variables,所以不該被放進 repository。 #ril - Installation — Flask 1.0.2 documentation
- Click 可以用來寫 CLI,它提供了
flask
command,還可以自訂其他 management command ?? - python-dotenv 讓
flask
command 支援 dotenv (.env
)
- Click 可以用來寫 CLI,它提供了
參考資料:
-
Form Validation with WTForms — Flask Documentation (0.12)
-
為了處理 form data,很快就會讓 code 變得很難讀,WTForms 就是在解決這個問題;另外 feature 了 Flask-WTF
-
首先要為 form 定義一個 class,不過這裡 "adding a separate module for the forms" 的建議似乎怪怪的?
-
Form 在 view function 裡用起來像是:
@app.route('/register', methods=['GET', 'POST']) def register(): form = RegistrationForm(request.form) if request.method == 'POST' and form.validate(): user = User(form.username.data, form.email.data, form.password.data) db_session.add(user) flash('Thanks for registering') return redirect(url_for('login')) return render_template('register.html', form=form)
-
無論如何都將
request.form
傳入 form class (當 form 是 GET 送出時,要改用request.args
);當 method 是 POST 時做驗證,是 GET (或驗證失敗時) 就拿去 render。 -
驗證用
validate()
(搭配request.method == 'POST' 的檢查,
validate_on_submit()是 Flask-WTF 加的),取得個別欄位的資料用
form..data
。 -
驗證成功會搭配
redirect()
轉向,若沒有成功則重新 render 一次;從這個角度來看,form 的 action 應該指向自己會比較好? 否則驗證失敗時 URL 會揭露一些原本沒打算讓 end user 看到的資訊,也可能被 bookmark?? -
在 template 裡是用 field function (form.()`) 畫出 form element。
-
-
Rendering - WTForms Documentation To render a field, simply call it, providing any values the widget expects as keyword arguments. 好特別的設定,通常 keyword arguments 對應到其他的 HTML attributes
- Development Server — Flask Documentation (0.12) #ril
- python - Why is flask CLI recommended over Flask.run? - Software Engineering Stack Exchange #ril
- Get Started, Part 2: Containers | Docker Documentation 以 Flask 做為範例 #ril
- Dockerize Simple Flask App — Container Tutorials #ril
-
uWSGI 是 Flask/WSGI application 跟 full-featured web server (例如 nginx、lighttpd 等) 介接的方式之一,uWSGI server 跟 web server 間是走 uwsgi protocol (小寫),在 Unix-like 上通常透過 socket 溝通;uWSGI server 會調用 application module/package 的 WSGI callable (
Flask
)。uwsgi --socket /tmp/yourapplication.sock --virtualenv venv \ --manage-script-name --mount /path=yourapplication:app \ --workers 4 --threads 4 location = /yourapplication { rewrite ^ /yourapplication/; } location /yourapplication { try_files $uri @yourapplication; } location @yourapplication { include uwsgi_params; uwsgi_pass unix:/tmp/yourapplication.sock; }
-
在 staging 環境,則可以搭配
FLASK_DEBUG=1
與--catch-exceptions
印出 backtrace:(若要在 uWSGI 下啟用 interactive debugger,要另外處理,跟--catch-exceptions
無關)FLASK_DEBUG=1 \ uwsgi --socket /tmp/yourapplication.sock --virtualenv venv \ --manage-script-name --mount /path=yourapplication:app \ --workers 4 --threads 4 \ --catch-exceptions
-
在本地端開發,也可以用 uWSGI 來模擬 staging/production 的環境,例如:(注意
--no-default-app
可以避免 app 在/
服務,跟 staging/production 不同,可能無法及早發覺某些錯誤)FLASK_DEBUG=1 \ uwsgi --http 127.0.0.1:5000 --virtualenv venv \ --manage-script-name --mount /path=yourapplication:app \ --workers 4 --threads 4 \ --catch-exceptions --no-default-app
參考資料:
- uWSGI — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/deploying/uwsgi/ 橋接 web server 與 WSGI application (通常走 socket),支援 uwsgi、HTTP 等不同 protocol,也直接支援 virtual environment。
- Native HTTP support — uWSGI 2.0 documentation http://uwsgi-docs.readthedocs.io/en/latest/HTTP.html
--http <address>:<port>
可以做為 HTTP server。
- 用 application package (而非 application module),底下可以拆分成多個 module;不過
FLASK_APP=package_name
的用法,必須要 "安裝" 過才行,還好pip install -e .
(搭配setup.py
) 可以安裝成 edit mode,開發時期修改程式會自動 reload 不成問題。 - 但用一個 startup module 帶出 package 不也是一樣? 而且不用安裝,搭配 blueprint 一樣可以把 route 拆到不同的 component。
參考資料:
- Larger Applications — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/patterns/packages/ 說明 "For larger applications it’s a good idea to use a package instead of a module."
- Modular Applications with Blueprints — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/blueprints/ #ril
Flask 0.11 後,執行 development server 建議執行 flask run
,搭配 FLASK_APP
指向 Python module 以取得 application object。
$ FLASK_APP=myapp flask run # 假如單純是個 module
$ FLASK_APP=myapp flask run # 假如複雜一點是個 package (myapp/__init__.py)
第二種狀況會遇到下面的錯誤:
Error: The file/path provided (myapp) does not appear to exist. Please verify the path is correct. If app is not on PYTHONPATH, ensure the extension is .py
雖然 Larger Applications 說採用 package 時要用 pip install --editable .
安裝過才行,但追查過 flask/cli.py
,發現 FLASK_APP=myapp/__init__.py flask run
的寫法也可以,或是改用 FLASK_APP=myapp python -m flask run
。
這項限制已在 PR #2414 處理掉,但要 Flask 1.0 才會釋出。
參考資料:
- Issue using Flask CLI with module layout recommended in Large Applications · Issue #1847 · pallets/flask #ril
- Creating a .wsgi file - mod_wsgi (Apache) — Flask Documentation (0.12)
.wsgi
file 只是用來取得 application object 的 Python module (跟採用 module/package 結構無關) - Command Line Interface — Flask Documentation (0.12) Flask 0.11 開始提供
flask
CLI,提到特殊情況下可用python -m flask
效果一樣。FLASK_APP
可以是 import path 或 "a filename of a Python module" - python - How to run Flask app as a package in PyCharm - Stack Overflow Flask 1.0 之前,必須將
FLASK_APP
指向/path/to/flask_app/__init__.py
。比較好的方式是安裝 package,然後將FLASK_APP
指向 package name,開發時可用pip install -e .
。 - FLASK_APP doesn't require .py extension for local packages by davidism · Pull Request #2414 · pallets/flask 明確指出 "This drops the requirement where FLASK_APP had to point to a .py file for packages that were not installed in develop mode. If the file is not importable, it will fail later in the loading process." 不過要 1.0 才有。
- Step 3: Installing flaskr as a Package — Flask Documentation (0.12) 也是要先安裝過
pip install --editable .
才能執行flask run
。 - Larger Applications — Flask Documentation (0.12) 大型 app 用 package,但要用
pip install -e .
安裝過。
$ flask --version
Flask 0.12.2
Python 2.7.10 (default, Jul 15 2017, 17:16:57)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]
bin/flask
:
# -*- coding: utf-8 -*-
import re
import sys
from flask.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
lib/python2.7/site-packages/flask/cli.py
:
def prepare_exec_for_file(filename):
"""Given a filename this will try to calculate the python path, add it
to the search path and return the actual module name that is expected.
"""
module = []
# Chop off file extensions or package markers 原來也接受 package/__init__.py 的寫法
if os.path.split(filename)[1] == '__init__.py':
filename = os.path.dirname(filename)
elif filename.endswith('.py'):
filename = filename[:-3]
else:
raise NoAppException('The file provided (%s) does exist but is not a '
'valid Python file. This means that it cannot '
'be used as application. Please change the '
'extension to .py' % filename)
filename = os.path.realpath(filename)
# 往上找到沒有 `__init__.py` 的那一層
dirpath = filename
while 1:
dirpath, extra = os.path.split(dirpath)
module.append(extra)
if not os.path.isfile(os.path.join(dirpath, '__init__.py')):
break
sys.path.insert(0, dirpath) # 調整 sys.path
return '.'.join(module[::-1])
def locate_app(app_id):
"""Attempts to locate the application."""
__traceback_hide__ = True
if ':' in app_id:
module, app_obj = app_id.split(':', 1)
else:
module = app_id
app_obj = None
# 這行可以看出 flask run 與 python -m flask run 的差異
print "app_id = %r, cwd = %r, sys.path[:2] = %r" % (app_id, os.getcwd(), sys.path[:2])
try:
__import__(module)
except ImportError:
# Reraise the ImportError if it occurred within the imported module.
# Determine this by checking whether the trace has a depth > 1.
if sys.exc_info()[-1].tb_next:
raise
else:
raise NoAppException('The file/path provided (%s) does not appear'
' to exist. Please verify the path is '
'correct. If app is not on PYTHONPATH, '
'ensure the extension is .py' % module)
mod = sys.modules[module]
if app_obj is None:
app = find_best_app(mod) # 找 app 或 application 或型態為 Flask 者
else:
app = getattr(mod, app_obj, None)
if app is None:
raise RuntimeError('Failed to find application in module "%s"'
% module)
return app
def find_default_import_path():
app = os.environ.get('FLASK_APP')
if app is None:
return
if os.path.isfile(app): # 是檔案才會調整 sys.path
return prepare_exec_for_file(app)
return app # 直接視為 module name
$ FLASK_APP=myapp flask run
app_id = 'myapp', cwd = '/Users/jeremykao/data/work/learning/flask/testing', sys.path[:2] = ['/Users/jeremykao/data/work/learning/flask/testing/venv-flask-testing/bin', '/Users/jeremykao/data/work/learning/flask/testing/venv-flask-testing/lib/python27.zip']
Usage: flask run [OPTIONS]
Error: The file/path provided (myapp) does not appear to exist. Please verify the path is correct. If app is not on PYTHONPATH, ensure the extension is .py
$ FLASK_APP=myapp python -m flask run
app_id = 'myapp', cwd = '/Users/jeremykao/data/work/learning/flask/testing', sys.path[:2] = ['', '/Users/jeremykao/data/work/learning/flask/testing/venv-flask-testing/lib/python27.zip']
- Serving Flask app "myapp"
- Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
flask run
與 python -m flask run
的差別在於 sys.path[0]
不同,不知道為什麼 sys.path[0]
會指向 flask
指令所在的目錄?
FLASK_DEBUG=1 flask run
發生錯誤時會調出 interactive debugger,但同樣的 app 用uwsgi --module myapp:app
啟用時,發生錯誤只會有 Internal Server Error? 大概是因為flask run
會參考FLASK_DEBUG
將app.debug
設為True
,並啟用 interactive debugger 的關係?uwsgi --catch-exceptions
搭配FLASK_DEBUG=1
(或在程式裡明確做app.debug = True
) 確實可以在出錯時看到 traeback,但卻沒有 interactive runner,猜想Flask
instance 內部會參考FLASK_DEBUG
要不要寫出 traceback,但 interactive debugger 則是flask run
啟用的。- 在程式裡加上下面的程式,並用
UWSGI_DEBUG
控制 (避免使用flask run
時套用兩層 debugger),就可以在 staging 環境用UWSGI_DEBUG=1 uwsgi ...
啟用 app,出問題時可以直接 debug。
if os.getenv('UWSGI_DEBUG'):
app.debug = True
from werkzeug.debug import DebuggedApplication
app.wsgi_app = DebuggedApplication(app.wsgi_app, evalex=True, pin_security=False)
參考資料:
-
python - Flask debug=True does not work when going through uWSGI - Stack Overflow https://stackoverflow.com/questions/10364854/ #ril
-
Edwardr: cannot use Flask's debug option with uWSGI, because it's not to be used in a forking environment. 呼應 Quick Start 裡 "Even though the interactive debugger does not work in forking environments (which makes it nearly impossible to use on production servers)" 的說法;在 uWSGI 裡可以用
--catch-exceptions
模擬 debugger? (只是印出 traceback) -
gonz: 那是 "werkzeug error page",所以要用
DebuggedApplication
middleware,另外 GaretJax 提到必須要用 single worker (--workers 1
)? 試過多個 worker 倒是沒什麼問題from werkzeug.debug import DebuggedApplication app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
-
-
Dubugging Flask applications under uWSGI (2012-10-28) https://codeseekah.com/2012/10/28/dubugging-flask-applications-under-uwsgi/ 不懂為什麼 Flask 的 debugger 在 uWSGI 下不能用? 這裡用
if app.debug:
來判斷要不要加上DebuggedApplication
,不過這仍會造成用flask run
執行時有兩層 debugger。 -
web application - Flask debug=True exploitation - Information Security Stack Exchange https://security.stackexchange.com/questions/140677/ 出現
DebuggedApplication(app, evalex=True, pin_security=False)
的用法,原來 PIN 是可以關閉的。 -
Quickstart — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/quickstart/ Even though the interactive debugger does not work in forking environments (which makes it nearly impossible to use on production servers) 好像有這麼回事? 上面 uWSGI 加上
DebuggedApplication
的做法,有時帶出的 debugger 是不太正常,但大部份時候又能作用 ...
參考資料:
- How to Handle Errors in Flask | Damyan's Blog (2015-06-11) https://damyanon.net/flask-series-logging/ 為
app.logger
增加一個 handler,另外也攔截了 500 error 與 unhandled exception #ril - Logging — Flask Documentation (0.13-dev) http://flask.pocoo.org/docs/dev/logging/ #ril
- Application Errors — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/errorhandling/ #ril
- 提到 "By default if your application runs in production mode, Flask will display a very simple page for you and log the exception to the logger." 指的是
app.logger
- 提到 "By default if your application runs in production mode, Flask will display a very simple page for you and log the exception to the logger." 指的是
- log exceptions using app.logger · Issue #192 · pallets/flask pallets/flask#192 #ril
- The Flask Mega-Tutorial, Part XVI: Debugging, Testing and Profiling - miguelgrinberg.com (2013-03-10) https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xvi-debugging-testing-and-profiling #ril
- Debugging Flask app with pdb (2015-05-28) http://www.pythonforhumans.com/notes/debugging-flask-app-with-pdb #ril
- python - Debugging flask with pdb - Stack Overflow https://stackoverflow.com/questions/26812150/ #ril
- Flask: drop into pdb on exception https://gist.github.com/alonho/4389137 #ril
- Python: Using pdb with Flask application - Stack Overflow https://stackoverflow.com/questions/15726340/ #ril
- 如何判斷 request 是 JSON,讀取 JSON 的內容? => 用
flask.Request.is_json
判斷 MIME type,用flask.Request.get_json()
取得 JSON 資料。 - 要如何回應 JSON??
- python - How to return json using Flask web framework - Stack Overflow http://stackoverflow.com/questions/13081532/ 傳回
flask.jsonify(*args, **kwargs)
即可。
- python - How to return json using Flask web framework - Stack Overflow http://stackoverflow.com/questions/13081532/ 傳回
- Custom Flask JSONEncoder | Flask (A Python Microframework) 覆寫
Flask.json_encoder
#ril - json_encoder - API — Flask 1.0.2 documentation alias of
flask.json.JSONEncoder
,沒講覆寫它可以自訂?? - flask.json.jsonify() - API — Flask 1.0.2 documentation #ril
- This function wraps
dumps()
to add a few enhancements that make life easier. It turns the JSON output into aResponse
object with theapplication/json
mimetype.
- This function wraps
- flask.json.dumps() - API — Flask 1.0.2 documentation #ril
- Serialize
obj
to a JSON formattedstr
by using the application’s configured encoder (json_encoder
) if there is an application on the stack. 看起來改寫Flask.json_encoder
是可以自訂 JSON encoder 的。
- Serialize
- 為什麼
flask.json.jsonify
的範例都寫from flask import jsonify
??
- Flask-Script — Flask-Script 0.4.0 documentation https://flask-script.readthedocs.io/en/latest/ 源自這個專案,但 Flask 0.11 後已經內建 CLI tool,應該可以取代? #ril
- Command Line Interface — Flask Documentation (0.12) http://flask.pocoo.org/docs/0.12/cli/ #ril
參考資料:
- miguelgrinberg/Flask-Migrate: SQLAlchemy database migrations for Flask applications using Alembic https://github.com/miguelgrinberg/Flask-Migrate 背後用 Alembic #ril
- 常用的指令有
./manage.py db upgrade head
昇級到最新版。
- Testing Flask Applications — Flask Documentation http://flask.pocoo.org/docs/latest/testing/ #ril
test_request_context()
似乎可以用實作 unit testing??
- 建議用 virtualenv 安裝
Flask
套件,安裝完成有flask
指令可以用。
參考資料:
- Welcome | Flask (A Python Microframework) http://flask.pocoo.org/
pip install Flask
即可安裝。 - Installation — Flask 1.0.2 documentation
- 支援 Python 3.4+, Python 2.7 及 PyPy;建議採最新版的 Python 3
- 會自動安裝 Werkzeug、Jinja、ItsDangerous 及 Clik,另外可以視需求安裝 Blinker、SimpleJSON、python-dotenv 及 Watchdog。
- Werkzeug 實作了 WSGI,做為 application 與 web server 間的標準 Python interface。
- Jinja 是一種 template language,用來畫 (render) web pages;Jinja 會帶出 MarkupSafe -- 將 untrusted input 做 escape,避免 injection 攻擊。
- ItsDangerous 可以做數位簽章 (signing),Flask 用它來確保 session cookie 沒有被動過手腳 (integrity)。
- Click 可以用來寫 CLI,它提供了
flask
command,還可以自訂其他 management command - Blinker 讓 Flask 支援 signal 的概念 ??
- SimpleJSON 相容於 Python 內建的
json
module,但是更快;如果有安裝的話,Flask 會優先採用它 - python-dotenv 讓
flask
command 支援 dotenv (.env
) - Watchdog 提供更好的 reloader 讓 development server 使用。
- 在 dev/prod 都建議用 virtual environment 管理 dependencies;Python 3 內建
venv
module,Python 2 則要加裝virtualenv
套件。
- Installation — Flask 0.12.4 documentation 支援 Python 2.6+ 與 Python 3,最快的方法 (kick-ass method) 是用 virtualenv。
書籍:
- Flask Blueprints - PACKT (2015-11)
更多:
手冊: