PoorWSGI 2.6.3__tar.gz → 2.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PKG-INFO +1 -1
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/PKG-INFO +1 -1
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/SOURCES.txt +1 -4
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/ChangeLog +18 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_footer.html +1 -1
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_poorwsgi_api.html +3 -1
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/documentation.rst +99 -55
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/large_file.py +15 -10
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/simple.py +37 -17
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/simple_json.py +12 -0
- poorwsgi-2.7.0/poorwsgi/fieldstorage.py +790 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/headers.py +39 -1
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/request.py +127 -292
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/response.py +97 -14
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/results.py +36 -28
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/session.py +0 -2
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/state.py +2 -2
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/wsgi.py +7 -8
- poorwsgi-2.7.0/tests/test_request.py +353 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_responses.py +5 -1
- poorwsgi-2.6.3/examples/async.py.skip +0 -36
- poorwsgi-2.6.3/examples/encoding.py.skip +0 -54
- poorwsgi-2.6.3/examples/jwt_example.py.skip +0 -86
- poorwsgi-2.6.3/examples/websocket-frames.py.skip +0 -80
- poorwsgi-2.6.3/tests/test_request.py +0 -210
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/CONTRIBUTION.rst +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/MANIFEST.in +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/dependency_links.txt +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/requires.txt +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/top_level.txt +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/README.rst +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_documentation.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_header.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_install.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_licence.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_poorwsgi.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_reference.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_text.html +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/about.rst +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/install.rst +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/licence.txt +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/small-logo.png +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/style.css +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/web.css +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/http_digest.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/metrics.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/openapi.json +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/openapi3.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/put_file.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/test.digest +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/websocket.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/__init__.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/digest.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/openapi_wrapper.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/py.typed +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/setup.cfg +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/setup.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_digest.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_header.py +0 -0
- {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_session.py +0 -0
|
@@ -24,10 +24,7 @@ doc/licence.txt
|
|
|
24
24
|
doc/small-logo.png
|
|
25
25
|
doc/style.css
|
|
26
26
|
doc/web.css
|
|
27
|
-
examples/async.py.skip
|
|
28
|
-
examples/encoding.py.skip
|
|
29
27
|
examples/http_digest.py
|
|
30
|
-
examples/jwt_example.py.skip
|
|
31
28
|
examples/large_file.py
|
|
32
29
|
examples/metrics.py
|
|
33
30
|
examples/openapi.json
|
|
@@ -36,10 +33,10 @@ examples/put_file.py
|
|
|
36
33
|
examples/simple.py
|
|
37
34
|
examples/simple_json.py
|
|
38
35
|
examples/test.digest
|
|
39
|
-
examples/websocket-frames.py.skip
|
|
40
36
|
examples/websocket.py
|
|
41
37
|
poorwsgi/__init__.py
|
|
42
38
|
poorwsgi/digest.py
|
|
39
|
+
poorwsgi/fieldstorage.py
|
|
43
40
|
poorwsgi/headers.py
|
|
44
41
|
poorwsgi/openapi_wrapper.py
|
|
45
42
|
poorwsgi/py.typed
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
==== 2.7.0 ====
|
|
2
|
+
* Reserved Request.db attribute for usage
|
|
3
|
+
* Right HTTPException type annotation
|
|
4
|
+
* Fix ETag header
|
|
5
|
+
* FieldStorage and FieldStorageParser implementation based on legacy
|
|
6
|
+
cgi.FieldStorage (#22)
|
|
7
|
+
- that means Python 3.13 support (#36)
|
|
8
|
+
* Stop Python 3.8 support - some mypy annotation didn't work
|
|
9
|
+
* Field refactoring - ! possible break change ! - sorry for that
|
|
10
|
+
- FieldStorageInterface - getvalue, getfist, getlist methods
|
|
11
|
+
- new func converter name, and no default converter
|
|
12
|
+
- all default values are not convert now
|
|
13
|
+
* Some old classes, functions, attributes and parameters are notified
|
|
14
|
+
as deprecated, and will be deleted in next major version.
|
|
15
|
+
* Automatic JSONResponse from dict or list, just like from text.
|
|
16
|
+
* Debug info, documentation and Error improvements.
|
|
17
|
+
* Automatic NoContentResponse from None (#28)
|
|
18
|
+
|
|
1
19
|
==== 2.6.3 ====
|
|
2
20
|
* HTTPException has status_code property
|
|
3
21
|
* Implicit value for not set cookie is None or empty dict for OpenAPI
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
{% set api = api + load_module('wsgi') %}
|
|
5
5
|
{% set api = api + load_module('request') %}
|
|
6
6
|
{% set api = api + load_module('response') %}
|
|
7
|
-
{% set api = api + load_module('
|
|
7
|
+
{% set api = api + load_module('headers') %}
|
|
8
|
+
{% set api = api + load_module('fieldstorage') %}
|
|
8
9
|
{% set api = api + load_module('session') %}
|
|
9
10
|
{% set api = api + load_module('digest') %}
|
|
11
|
+
{% set api = api + load_module('results') %}
|
|
10
12
|
{% set api = api + load_module('state') %}
|
|
11
13
|
{% set api = api + load_module('openapi_wrapper') %}
|
|
12
14
|
|
|
@@ -106,8 +106,15 @@ make_response function.
|
|
|
106
106
|
headers=None, status_code=HTTP_OK)
|
|
107
107
|
|
|
108
108
|
|
|
109
|
-
data : str, bytes, generator
|
|
110
|
-
Returned value as response body.
|
|
109
|
+
data : str, bytes, dict, list, None or generator
|
|
110
|
+
Returned value as response body. Each type of data returns different
|
|
111
|
+
response type:
|
|
112
|
+
|
|
113
|
+
- str, bytes - Response
|
|
114
|
+
- dict, list - JSONResponse
|
|
115
|
+
- None - NoContentReponse
|
|
116
|
+
- generator - GeneratorResponse
|
|
117
|
+
|
|
111
118
|
content_type : str
|
|
112
119
|
The ``Content-Type`` header which is set, if this header is not set
|
|
113
120
|
in headers.
|
|
@@ -122,11 +129,28 @@ You can use headers instead of `content_type` argument.
|
|
|
122
129
|
.. code:: python
|
|
123
130
|
|
|
124
131
|
@app.http_state(NOT_FOUND)
|
|
125
|
-
def not_found(req):
|
|
132
|
+
def not_found(req, *_):
|
|
126
133
|
return make_response(b'Page not Found',
|
|
127
134
|
headers={"Content-Type": "text/plain"},
|
|
128
135
|
status_code=NOT_FOUND)
|
|
129
136
|
|
|
137
|
+
If you return just simple type, or tuple of arguments, PoorWSGI automatically
|
|
138
|
+
call make_response function to create response for you.
|
|
139
|
+
|
|
140
|
+
.. code:: python
|
|
141
|
+
|
|
142
|
+
@app.route("/json")
|
|
143
|
+
def not_found(req):
|
|
144
|
+
"""Return JSONResponse"""
|
|
145
|
+
return {"msg": "Message", "type": "object"}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.route('/gone')
|
|
149
|
+
def gone(req):
|
|
150
|
+
"""Return NoContentResponse"""
|
|
151
|
+
return None, "", None, HTTP_GONE
|
|
152
|
+
|
|
153
|
+
|
|
130
154
|
Response
|
|
131
155
|
````````
|
|
132
156
|
Response object is one of base element of WSGI application. Response is object
|
|
@@ -167,6 +191,27 @@ This response returned these data with status code 418:
|
|
|
167
191
|
"numbers": [0, 1, 2, 3, 4]
|
|
168
192
|
}
|
|
169
193
|
|
|
194
|
+
Or you can simple return dictionary or list. It will be automatically convert
|
|
195
|
+
to JSONResponse by make_response function. So it is similar to return text or
|
|
196
|
+
bytes.
|
|
197
|
+
|
|
198
|
+
Be careful that your dict or list **have to be convertible** to JSON by
|
|
199
|
+
json.dumps function.
|
|
200
|
+
|
|
201
|
+
.. code:: python
|
|
202
|
+
|
|
203
|
+
@app.route('/dict')
|
|
204
|
+
def get_dict(_):
|
|
205
|
+
"""Return dictionary"""
|
|
206
|
+
return {"route": "/dict", "type": "dict"}
|
|
207
|
+
|
|
208
|
+
@app.route('/list')
|
|
209
|
+
def get_list(_):
|
|
210
|
+
"""Return list"""
|
|
211
|
+
return [["key", "value"], ["route", "/list"], ["type", "list"]]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
170
215
|
JSONGeneratorResponse
|
|
171
216
|
`````````````````````
|
|
172
217
|
There is JSONGeneratorResponse class too, which could return JSON, but
|
|
@@ -249,7 +294,7 @@ parameters or as constructor argument.
|
|
|
249
294
|
return NotModifiedResponse(date=time_to_http())
|
|
250
295
|
|
|
251
296
|
return FileResponse(req.document_root+"/filename",
|
|
252
|
-
headers={'
|
|
297
|
+
headers={'ETag': etag})
|
|
253
298
|
|
|
254
299
|
Partial Content
|
|
255
300
|
```````````````
|
|
@@ -501,18 +546,20 @@ or METHOD_HEAD.
|
|
|
501
546
|
|
|
502
547
|
HTTP state handlers
|
|
503
548
|
~~~~~~~~~~~~~~~~~~~
|
|
504
|
-
There are some predefined HTTP state handlers, which
|
|
549
|
+
There are some predefined HTTP state handlers, which are use when other
|
|
505
550
|
HTTP state are raised via HTTPException or any other exception which ends with
|
|
506
551
|
HTTP_INTERNAL_SERVER_ERROR status code.
|
|
507
552
|
|
|
508
553
|
You can redefined your own handlers for any combination of status code and
|
|
509
|
-
method type like routes handlers.
|
|
554
|
+
method type like routes handlers. Response from these handlers are same as in
|
|
510
555
|
route handlers.
|
|
511
556
|
|
|
557
|
+
Be sure, that some http_state handlers can add other keyword arguments.
|
|
558
|
+
|
|
512
559
|
.. code:: python
|
|
513
560
|
|
|
514
561
|
@app.http_state(state.HTTP_NOT_FOUND)
|
|
515
|
-
def page_not_found(req):
|
|
562
|
+
def page_not_found(req, *_):
|
|
516
563
|
return "Your request %s not found." % req.path, "text/plain"
|
|
517
564
|
|
|
518
565
|
If your http state (error) handler was crashed with error, internal server
|
|
@@ -617,7 +664,7 @@ poor_AutoArgs is set to On, which is default.
|
|
|
617
664
|
@app.route('/test/get')
|
|
618
665
|
def test_get(req)
|
|
619
666
|
name = req.args.getfirst('name')
|
|
620
|
-
colors = req.args.getlist('color',
|
|
667
|
+
colors = req.args.getlist('color', func=int)
|
|
621
668
|
return "Get arguments are %s" % str(req.args)
|
|
622
669
|
|
|
623
670
|
If no arguments are parsed, or if poor_AutoArgs is set to Off, req.args is
|
|
@@ -625,16 +672,16 @@ EmptyForm instance, which is dict base class too with both of methods.
|
|
|
625
672
|
|
|
626
673
|
Form arguments
|
|
627
674
|
~~~~~~~~~~~~~~
|
|
628
|
-
Request form
|
|
629
|
-
poorwsgi.
|
|
630
|
-
cgi module.
|
|
631
|
-
On, which is default, request method is POST, PUT or PATCH and request
|
|
675
|
+
Request form arguments are stored in FieldStorage class, define in
|
|
676
|
+
poorwsgi.fieldstorage module. This class is inspired by FieldStorage from
|
|
677
|
+
legacy cgi module. Variables are parsed every time, when poor_AutoForm is set
|
|
678
|
+
to On, which is default, request method is POST, PUT or PATCH and request
|
|
632
679
|
mime type is one of `Application.form_mime_types`. You can call it
|
|
633
680
|
on any other methods of course, but it must exist wsgi.input in request
|
|
634
681
|
environment from wsgi server.
|
|
635
682
|
|
|
636
683
|
req.form instance is create with poor_KeepBlankValues and poor_StrictParsing
|
|
637
|
-
variables as Args class is create, but
|
|
684
|
+
variables as Args class is create, but FieldStorageParser have file_callback
|
|
638
685
|
variable, which is configurable by Application.file_callback property.
|
|
639
686
|
|
|
640
687
|
.. code:: python
|
|
@@ -645,7 +692,7 @@ variable, which is configurable by Application.file_callback property.
|
|
|
645
692
|
# is convert to number with zero
|
|
646
693
|
# as default
|
|
647
694
|
name = req.form.getfirst('name')
|
|
648
|
-
colors = req.form.getlist('color',
|
|
695
|
+
colors = req.form.getlist('color', func=int)
|
|
649
696
|
return "Post arguments for id are %s" % (id, str(req.args))
|
|
650
697
|
|
|
651
698
|
As like Args class, if poor_AutoForm is set to Off, or if method is no POST,
|
|
@@ -705,14 +752,13 @@ There are a few variants which req.json could be:
|
|
|
705
752
|
|
|
706
753
|
File uploading
|
|
707
754
|
~~~~~~~~~~~~~~
|
|
708
|
-
By default,
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
but.
|
|
755
|
+
By default, FieldStorage store files somewhere to ``/tmp`` directory. This is
|
|
756
|
+
happened in FieldStorageParser, which calls ``TemporaryFile``. Uploaded files
|
|
757
|
+
are accessible like another form variables, but.
|
|
712
758
|
|
|
713
759
|
Any variables from FieldStorage is accessible with ``__getitem__`` method.
|
|
714
760
|
So you can get variable by ``req.form[key]``, which gets FieldStorage
|
|
715
|
-
instance. This instance has some
|
|
761
|
+
instance. This instance has some attributes, which you can test,
|
|
716
762
|
what type of variable is it.
|
|
717
763
|
|
|
718
764
|
.. code:: python
|
|
@@ -742,16 +788,16 @@ Application.file_callback property.
|
|
|
742
788
|
As you can see, this example works, but it is so bad solution of your problem.
|
|
743
789
|
Little bit better solution will be, if you store files only if exist and only
|
|
744
790
|
to special separate dictionary, which could be configurable. That you need use
|
|
745
|
-
factory to create file_callback. In next example is
|
|
746
|
-
which is not important, when `file_callback` could be set via
|
|
747
|
-
property.
|
|
791
|
+
to factory to create file_callback. In next example is written own form
|
|
792
|
+
processing, which is not important, when `file_callback` could be set via
|
|
793
|
+
Application property.
|
|
748
794
|
|
|
749
795
|
.. code:: python
|
|
750
796
|
|
|
751
797
|
from io import FileIO
|
|
752
798
|
from os.path import exists
|
|
753
799
|
|
|
754
|
-
from poorwsgi import Application, state,
|
|
800
|
+
from poorwsgi import Application, state, fieldstorage
|
|
755
801
|
|
|
756
802
|
app = Application('test')
|
|
757
803
|
|
|
@@ -783,19 +829,21 @@ property.
|
|
|
783
829
|
if req.method_number == state.METHOD_POST:
|
|
784
830
|
factory = StorageFactory('./upload')
|
|
785
831
|
try:
|
|
786
|
-
|
|
787
|
-
req,
|
|
832
|
+
parser = FieldStorageParser(
|
|
833
|
+
req.input, req.heades,
|
|
788
834
|
keep_blank_values=app.keep_blank_values,
|
|
789
835
|
strict_parsing=app.strict_parsing,
|
|
790
836
|
file_callback=factory.create)
|
|
837
|
+
req.form = parser.parser()
|
|
791
838
|
except Exception as e:
|
|
792
839
|
req.log_error(e)
|
|
793
840
|
|
|
794
841
|
CachedInput
|
|
795
842
|
~~~~~~~~~~~
|
|
796
843
|
|
|
797
|
-
When HTTP Forms are base64 encoded,
|
|
798
|
-
file. This is not so optimal. So there is CachedInput class, which
|
|
844
|
+
When HTTP Forms are base64 encoded, FieldStorageParser use readline on request
|
|
845
|
+
input file. This is not so optimal. So there is CachedInput class, which
|
|
846
|
+
is returned as wrapper around ``wsgi.input`` file.
|
|
799
847
|
|
|
800
848
|
Proccess variables
|
|
801
849
|
~~~~~~~~~~~~~~~~~~
|
|
@@ -818,14 +866,14 @@ If auto_form is set to ``True``, which is default, Request object parse input
|
|
|
818
866
|
arguments from request body at initialisation when request type is POST, PUT
|
|
819
867
|
or PATCH. There will be ``Request.form`` property which is instance of
|
|
820
868
|
``FieldStorage`` class. If you want to off this functionality, set this property
|
|
821
|
-
to ``False``. If form parsing is disabled, or
|
|
869
|
+
to ``False``. If form parsing is disabled, or JSON is detected, ``Request.form``
|
|
822
870
|
will be instance of ``EmptyForm`` with same interface and no data.
|
|
823
871
|
|
|
824
872
|
Application.form_mime_types
|
|
825
873
|
``````````````````````````````
|
|
826
|
-
List of mime types, which is
|
|
827
|
-
If input request does not have set one of these mime types, that form
|
|
828
|
-
parsed.
|
|
874
|
+
List of mime types, which is parsed as input form by ``FieldStorageParser``
|
|
875
|
+
class. If input request does not have set one of these mime types, that form
|
|
876
|
+
will not be parsed.
|
|
829
877
|
|
|
830
878
|
Application.file_callback
|
|
831
879
|
`````````````````````````
|
|
@@ -848,15 +896,15 @@ If input request does not have set one of these mime types, that
|
|
|
848
896
|
Application.keep_blank_values
|
|
849
897
|
`````````````````````````````
|
|
850
898
|
This property is set for input parameters to automatically calling Args and
|
|
851
|
-
|
|
852
|
-
property is set to ``0``. If it set to ``1``, blank values should be
|
|
853
|
-
as empty strings.
|
|
899
|
+
FieldStorageParser classes, when auto_args resp. auto_form is set. By default
|
|
900
|
+
this property is set to ``0``. If it set to ``1``, blank values should be
|
|
901
|
+
interpret as empty strings.
|
|
854
902
|
|
|
855
903
|
Application.strict_parsing
|
|
856
904
|
``````````````````````````
|
|
857
905
|
This property is set for input parameter to automatically calling Args and
|
|
858
|
-
|
|
859
|
-
variable is set to ``0``. If is set to ``1``, ValueError exception
|
|
906
|
+
FieldStorageParser classes. When auto_args resp. auto_form is set. By default
|
|
907
|
+
this variable is set to ``0``. If is set to ``1``, ValueError exception
|
|
860
908
|
could raise on parsing error. I'm sure, that you never want to set this
|
|
861
909
|
variable to ``1``. If so, use it in your own parsing.
|
|
862
910
|
|
|
@@ -878,10 +926,11 @@ variable to ``1``. If so, use it in your own parsing.
|
|
|
878
926
|
|
|
879
927
|
if req.method_number == state.METHOD_POST:
|
|
880
928
|
try:
|
|
881
|
-
|
|
882
|
-
req,
|
|
929
|
+
parser = fieldstorage.FieldStorageParser(
|
|
930
|
+
req.input, req.headers,
|
|
883
931
|
keep_blank_values=app.keep_blank_values,
|
|
884
932
|
strict_parsing=app.strict_parsing)
|
|
933
|
+
req.form = parser.parse()
|
|
885
934
|
except Exception as e:
|
|
886
935
|
logging.error("Bad request body: %s", e)
|
|
887
936
|
|
|
@@ -894,7 +943,7 @@ empty tupple will be set.
|
|
|
894
943
|
|
|
895
944
|
Application / User options
|
|
896
945
|
--------------------------
|
|
897
|
-
Like in mod_python Request, Poor WSGI
|
|
946
|
+
Like in mod_python Request, Poor WSGI Application have get_options method too.
|
|
898
947
|
This method return dictionary of application options or variables, which start
|
|
899
948
|
with ``app_`` prefix. This prefix is cut from options names.
|
|
900
949
|
|
|
@@ -910,18 +959,11 @@ And you can get these variables with get_options method:
|
|
|
910
959
|
|
|
911
960
|
.. code:: python
|
|
912
961
|
|
|
913
|
-
config =
|
|
914
|
-
|
|
915
|
-
@app.before_response()
|
|
916
|
-
def load_options(req):
|
|
917
|
-
global config
|
|
918
|
-
if config is None:
|
|
919
|
-
config = req.get_options()
|
|
920
|
-
req.config = config
|
|
962
|
+
config = app.get_options()
|
|
921
963
|
|
|
922
964
|
@app.route('/options')
|
|
923
965
|
def list_options(req):
|
|
924
|
-
return ("%s = %s" % (key, val) in
|
|
966
|
+
return ("%s = %s" % (key, val) in config.items())
|
|
925
967
|
|
|
926
968
|
Output of application url /options looks like:
|
|
927
969
|
|
|
@@ -931,13 +973,15 @@ Output of application url /options looks like:
|
|
|
931
973
|
tmp_path = tmp
|
|
932
974
|
templ = templ
|
|
933
975
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
default:
|
|
976
|
+
You can store your variables to request object too. There are few reserved
|
|
977
|
+
variables for you, which poorwsgi never use, and which are None by default:
|
|
937
978
|
|
|
938
|
-
:req.
|
|
939
|
-
|
|
940
|
-
:req.
|
|
979
|
+
:req.user: For user object, who is login, check_digest decorator set this
|
|
980
|
+
variable.
|
|
981
|
+
:req.api: For API checking. OpenAPIRequest use this variable.
|
|
982
|
+
:req.db: For single database conection per request. You can store structure
|
|
983
|
+
with more databases if you need to this vairable.
|
|
984
|
+
:req.app\_: As prefix for any your application variable.
|
|
941
985
|
|
|
942
986
|
So if you want to add any other variable, be careful to named it.
|
|
943
987
|
|
|
@@ -1030,7 +1074,7 @@ Application.secret_key property.
|
|
|
1030
1074
|
@app.route('/login', method=state.METHOD_GET_POST)
|
|
1031
1075
|
def login(req):
|
|
1032
1076
|
if req.method == 'POST':
|
|
1033
|
-
passwd = req.form.getfirst('passwd',
|
|
1077
|
+
passwd = req.form.getfirst('passwd', func=str)
|
|
1034
1078
|
if passwd != 'SecretPasswds':
|
|
1035
1079
|
log.info('Bad password')
|
|
1036
1080
|
redirect('/login', text='Bad password')
|
|
@@ -15,9 +15,9 @@ EXAMPLES_PATH = os.path.dirname(__file__)
|
|
|
15
15
|
sys.path.insert(0, os.path.abspath(
|
|
16
16
|
os.path.join(EXAMPLES_PATH, os.path.pardir)))
|
|
17
17
|
|
|
18
|
-
# pylint: disable=wrong-import-position
|
|
18
|
+
# pylint: disable=import-error, disable=wrong-import-position
|
|
19
19
|
from poorwsgi import Application, state # noqa
|
|
20
|
-
from poorwsgi.
|
|
20
|
+
from poorwsgi.fieldstorage import FieldStorageParser # noqa
|
|
21
21
|
from poorwsgi.response import HTTPException # noqa
|
|
22
22
|
from poorwsgi.results import hbytes # noqa
|
|
23
23
|
|
|
@@ -27,8 +27,6 @@ app = application = Application("large_file")
|
|
|
27
27
|
app.debug = True
|
|
28
28
|
app.auto_form = False
|
|
29
29
|
|
|
30
|
-
# pylint: disable=consider-using-f-string
|
|
31
|
-
|
|
32
30
|
|
|
33
31
|
class Blackhole:
|
|
34
32
|
"""Dummy File Object"""
|
|
@@ -55,6 +53,9 @@ class Blackhole:
|
|
|
55
53
|
"""Return sha256 hexdigest of file."""
|
|
56
54
|
return self.__hash.hexdigest()
|
|
57
55
|
|
|
56
|
+
def close(self):
|
|
57
|
+
"""Dummy close"""
|
|
58
|
+
|
|
58
59
|
|
|
59
60
|
class Temporary:
|
|
60
61
|
"""Temporary file"""
|
|
@@ -143,15 +144,19 @@ def html_form(req, file_callback):
|
|
|
143
144
|
to_download = min(req.content_length-bytes_read, 65365)
|
|
144
145
|
data = req.read(to_download)
|
|
145
146
|
elif file_callback == original_factory:
|
|
146
|
-
|
|
147
|
-
req,
|
|
147
|
+
parser = FieldStorageParser(
|
|
148
|
+
req.input, req.headers,
|
|
149
|
+
keep_blank_values=app.keep_blank_values,
|
|
148
150
|
strict_parsing=app.strict_parsing)
|
|
151
|
+
parser.parse()
|
|
149
152
|
else:
|
|
150
|
-
|
|
151
|
-
req,
|
|
153
|
+
parser = FieldStorageParser(
|
|
154
|
+
req.input, req.headers,
|
|
155
|
+
keep_blank_values=app.keep_blank_values,
|
|
152
156
|
strict_parsing=app.strict_parsing,
|
|
153
157
|
file_callback=file_callback(req))
|
|
154
|
-
|
|
158
|
+
form = parser.parse()
|
|
159
|
+
bytes_read = parser.bytes_read
|
|
155
160
|
hexdigest = form['file'].file.hexdigest()
|
|
156
161
|
|
|
157
162
|
end = time() - start
|
|
@@ -251,5 +256,5 @@ class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
|
|
|
251
256
|
if __name__ == '__main__':
|
|
252
257
|
ADDRESS = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
|
|
253
258
|
httpd = make_server(ADDRESS, 8080, app, ThreadingWSGIServer)
|
|
254
|
-
print("Starting to serve on http
|
|
259
|
+
print(f"Starting to serve on http://{ADDRESS}:8080")
|
|
255
260
|
httpd.serve_forever()
|
|
@@ -22,7 +22,8 @@ python_path.insert(
|
|
|
22
22
|
0, os.path.abspath(os.path.join(EXAMPLES_PATH, os.path.pardir)))
|
|
23
23
|
|
|
24
24
|
# pylint: disable=import-error, wrong-import-position
|
|
25
|
-
from poorwsgi import Application, redirect,
|
|
25
|
+
from poorwsgi import Application, redirect, state # noqa
|
|
26
|
+
from poorwsgi.fieldstorage import FieldStorageParser # noqa
|
|
26
27
|
from poorwsgi.headers import http_to_time, parse_range, time_to_http # noqa
|
|
27
28
|
from poorwsgi.response import FileResponse # noqa
|
|
28
29
|
from poorwsgi.response import HTTPException # noqa
|
|
@@ -89,6 +90,7 @@ app.auto_form = False
|
|
|
89
90
|
def log_request(req):
|
|
90
91
|
"""Log each request before processing."""
|
|
91
92
|
log.info("Before response")
|
|
93
|
+
log.info("Headers: %s", req.headers)
|
|
92
94
|
log.info("Data: %s", req.data)
|
|
93
95
|
|
|
94
96
|
|
|
@@ -100,11 +102,12 @@ def auto_form(req):
|
|
|
100
102
|
if req.is_body_request or req.server_protocol == "HTTP/0.9":
|
|
101
103
|
factory = StorageFactory('./upload')
|
|
102
104
|
try:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
parser = FieldStorageParser(
|
|
106
|
+
req.input, req.headers,
|
|
107
|
+
keep_blank_values=app.keep_blank_values,
|
|
108
|
+
strict_parsing=app.strict_parsing,
|
|
109
|
+
file_callback=factory.create)
|
|
110
|
+
req.form = parser.parse()
|
|
108
111
|
except Exception as err: # pylint: disable=broad-except
|
|
109
112
|
log.exception("Exception %s", str(err))
|
|
110
113
|
raise
|
|
@@ -269,7 +272,7 @@ def style(_):
|
|
|
269
272
|
@app.route('/test/static')
|
|
270
273
|
def test_dynamic(req, variable=None):
|
|
271
274
|
"""Test dynamics values."""
|
|
272
|
-
if not variable and req.headers.get('
|
|
275
|
+
if not variable and req.headers.get('ETag') == 'W/"0123"':
|
|
273
276
|
return not_modified(req)
|
|
274
277
|
|
|
275
278
|
var_info = {
|
|
@@ -299,7 +302,7 @@ def test_dynamic(req, variable=None):
|
|
|
299
302
|
("</table>",) + \
|
|
300
303
|
get_footer()
|
|
301
304
|
|
|
302
|
-
response = Response(headers={'
|
|
305
|
+
response = Response(headers={'ETag': 'W/"0123"'})
|
|
303
306
|
for line in buff:
|
|
304
307
|
response.write(line + '\n')
|
|
305
308
|
return response
|
|
@@ -395,15 +398,27 @@ def test_form(req):
|
|
|
395
398
|
'<input type="text" name="gx" value="3"><br/>',
|
|
396
399
|
'<input type="submit" name="btn" value="Send">'
|
|
397
400
|
'</form>',
|
|
398
|
-
"<h2>Post Form</h2>",
|
|
399
|
-
'<form method="post">',
|
|
401
|
+
"<h2>Post Form multipart/form-data</h2>",
|
|
402
|
+
'<form method="post" enctype="multipart/form-data">',
|
|
403
|
+
'<input type="text" name="fname" value="Ondřej"><br/>',
|
|
404
|
+
'<input type="text" name="fsurname" value="Tůma"><br/>',
|
|
405
|
+
'<input type="text" name="fx" value="8">'
|
|
406
|
+
'<input type="text" name="fx" value="7">'
|
|
407
|
+
'<input type="text" name="fx" value="6"><br/>',
|
|
408
|
+
'<textarea name="fbody">Some\ntext</textarea><br/>'
|
|
409
|
+
'<input type="submit" name="btn" value="Send">'
|
|
410
|
+
'</form>',
|
|
411
|
+
"<h2>Post Form application/x-www-form-urlencoded</h2>",
|
|
412
|
+
'<form method="post" enctype="application/x-www-form-urlencoded">',
|
|
400
413
|
'<input type="text" name="pname" value="Ondřej"><br/>',
|
|
401
414
|
'<input type="text" name="psurname" value="Tůma"><br/>',
|
|
402
415
|
'<input type="text" name="px" value="8">'
|
|
403
416
|
'<input type="text" name="px" value="7">'
|
|
404
417
|
'<input type="text" name="px" value="6"><br/>',
|
|
418
|
+
'<textarea name="fbody">Some\ntext</textarea><br/>'
|
|
405
419
|
'<input type="submit" name="btn" value="Send">'
|
|
406
420
|
'</form>',
|
|
421
|
+
|
|
407
422
|
"<h2>Variables</h2>",
|
|
408
423
|
"<table>") + \
|
|
409
424
|
tuple("<tr><td>%s:</td><td>%s</td></tr>" %
|
|
@@ -495,7 +510,7 @@ def test_upload(req):
|
|
|
495
510
|
|
|
496
511
|
|
|
497
512
|
@app.http_state(state.HTTP_NOT_FOUND)
|
|
498
|
-
def not_found(req):
|
|
513
|
+
def not_found(req, *_):
|
|
499
514
|
"""Not found example response."""
|
|
500
515
|
buff = (
|
|
501
516
|
"<html>",
|
|
@@ -517,10 +532,9 @@ def not_found(req):
|
|
|
517
532
|
|
|
518
533
|
|
|
519
534
|
@app.error_handler(ValueError)
|
|
520
|
-
def value_error_handler(
|
|
535
|
+
def value_error_handler(*_):
|
|
521
536
|
"""ValueError exception handler example."""
|
|
522
|
-
|
|
523
|
-
print("ValueError: ", error)
|
|
537
|
+
log.exception("ValueError")
|
|
524
538
|
raise HTTPException(state.HTTP_BAD_REQUEST)
|
|
525
539
|
|
|
526
540
|
|
|
@@ -640,7 +654,7 @@ def simple_py(req):
|
|
|
640
654
|
if last_modified <= if_modified:
|
|
641
655
|
return NotModifiedResponse(date=time_to_http())
|
|
642
656
|
|
|
643
|
-
response = FileResponse(__file__, headers={'
|
|
657
|
+
response = FileResponse(__file__, headers={'ETag': etag})
|
|
644
658
|
ranges = {}
|
|
645
659
|
if 'Range' in req.headers:
|
|
646
660
|
ranges = parse_range(req.headers['Range'])
|
|
@@ -662,8 +676,8 @@ def method_raises_errror(_):
|
|
|
662
676
|
raise RuntimeError('Test of internal server error')
|
|
663
677
|
|
|
664
678
|
|
|
665
|
-
@app.route('/none
|
|
666
|
-
def
|
|
679
|
+
@app.route('/none')
|
|
680
|
+
def none_no_content(_):
|
|
667
681
|
"""Test for None response."""
|
|
668
682
|
|
|
669
683
|
|
|
@@ -680,6 +694,12 @@ def forbidden(req):
|
|
|
680
694
|
raise HTTPException(state.HTTP_FORBIDDEN)
|
|
681
695
|
|
|
682
696
|
|
|
697
|
+
@app.route('/not-modified')
|
|
698
|
+
def not_modified_result(_):
|
|
699
|
+
"""Test for raise not NotModifiedResponse"""
|
|
700
|
+
raise HTTPException(NotModifiedResponse(etag="012"))
|
|
701
|
+
|
|
702
|
+
|
|
683
703
|
@app.route('/not-implemented')
|
|
684
704
|
def not_implemented(req):
|
|
685
705
|
"""Test not implemented exception"""
|
|
@@ -87,6 +87,18 @@ def get_unicode(_):
|
|
|
87
87
|
encoder_kwargs={"ensure_ascii": False})
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
@app.route('/dict')
|
|
91
|
+
def get_dict(_):
|
|
92
|
+
"""Return dictionary"""
|
|
93
|
+
return {"route": "/dict", "type": "dict"}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@app.route('/list')
|
|
97
|
+
def get_list(_):
|
|
98
|
+
"""Return list"""
|
|
99
|
+
return [["key", "value"], ["route", "/list"], ["type", "list"]]
|
|
100
|
+
|
|
101
|
+
|
|
90
102
|
@app.route('/test/headers')
|
|
91
103
|
def test_headers(req):
|
|
92
104
|
"""Request headers response."""
|