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.
Files changed (60) hide show
  1. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PKG-INFO +1 -1
  2. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/PKG-INFO +1 -1
  3. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/SOURCES.txt +1 -4
  4. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/ChangeLog +18 -0
  5. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_footer.html +1 -1
  6. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_poorwsgi_api.html +3 -1
  7. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/documentation.rst +99 -55
  8. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/large_file.py +15 -10
  9. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/simple.py +37 -17
  10. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/simple_json.py +12 -0
  11. poorwsgi-2.7.0/poorwsgi/fieldstorage.py +790 -0
  12. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/headers.py +39 -1
  13. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/request.py +127 -292
  14. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/response.py +97 -14
  15. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/results.py +36 -28
  16. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/session.py +0 -2
  17. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/state.py +2 -2
  18. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/wsgi.py +7 -8
  19. poorwsgi-2.7.0/tests/test_request.py +353 -0
  20. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_responses.py +5 -1
  21. poorwsgi-2.6.3/examples/async.py.skip +0 -36
  22. poorwsgi-2.6.3/examples/encoding.py.skip +0 -54
  23. poorwsgi-2.6.3/examples/jwt_example.py.skip +0 -86
  24. poorwsgi-2.6.3/examples/websocket-frames.py.skip +0 -80
  25. poorwsgi-2.6.3/tests/test_request.py +0 -210
  26. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/CONTRIBUTION.rst +0 -0
  27. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/MANIFEST.in +0 -0
  28. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/dependency_links.txt +0 -0
  29. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/requires.txt +0 -0
  30. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/PoorWSGI.egg-info/top_level.txt +0 -0
  31. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/README.rst +0 -0
  32. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_documentation.html +0 -0
  33. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_header.html +0 -0
  34. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_install.html +0 -0
  35. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_licence.html +0 -0
  36. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_poorwsgi.html +0 -0
  37. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_reference.html +0 -0
  38. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/_text.html +0 -0
  39. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/about.rst +0 -0
  40. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/install.rst +0 -0
  41. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/licence.txt +0 -0
  42. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/small-logo.png +0 -0
  43. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/style.css +0 -0
  44. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/doc/web.css +0 -0
  45. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/http_digest.py +0 -0
  46. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/metrics.py +0 -0
  47. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/openapi.json +0 -0
  48. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/openapi3.py +0 -0
  49. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/put_file.py +0 -0
  50. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/test.digest +0 -0
  51. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/examples/websocket.py +0 -0
  52. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/__init__.py +0 -0
  53. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/digest.py +0 -0
  54. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/openapi_wrapper.py +0 -0
  55. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/poorwsgi/py.typed +0 -0
  56. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/setup.cfg +0 -0
  57. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/setup.py +0 -0
  58. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_digest.py +0 -0
  59. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_header.py +0 -0
  60. {poorwsgi-2.6.3 → poorwsgi-2.7.0}/tests/test_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PoorWSGI
3
- Version: 2.6.3
3
+ Version: 2.7.0
4
4
  Summary: Poor WSGI connector for Python
5
5
  Home-page: http://poorhttp.zeropage.cz/poorwsgi
6
6
  Author: Ondřej Tůma
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PoorWSGI
3
- Version: 2.6.3
3
+ Version: 2.7.0
4
4
  Summary: Poor WSGI connector for Python
5
5
  Home-page: http://poorhttp.zeropage.cz/poorwsgi
6
6
  Author: Ondřej Tůma
@@ -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
@@ -2,7 +2,7 @@
2
2
  <div class="line"></div>
3
3
  <div class="credit">
4
4
  Create by Ondřej Tůma (<a href="http://ipv6.mcbig.cz">McBig</a>).
5
- &copy; 2020. Generate by
5
+ &copy; 2024. Generate by
6
6
  <a href="http://poorhttp.zeropage.cz/jinja24doc.html">Jinja24doc</a>.
7
7
  </div>
8
8
  </div>
@@ -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('results') %}
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={'E-Tag': etag})
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 is use when other
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. Responsing from these handlers are same as in
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', fce=int)
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 areguments are stored to FieldStorage class, define in
629
- poorwsgi.request module. This class is based on FieldStorage from standard
630
- cgi module. And variables are parsed every time, when poor_AutoForm is set to
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 FieldStorage have file_callback
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', fce=int)
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, python's FieldStorage, so poorwsgi.FieldStorage too, store files
709
- somewhere to ``/tmp`` directory. This is happened in FieldStorage, which calls
710
- ``TemporaryFile``. Uploaded files are accessible like another form variables,
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 another variables, which you can test,
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 write own form parsering,
746
- which is not important, when `file_callback` could be set via Application
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, request
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
- req.form = request.FieldStorage(
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, FieldStorage use readline on request input
798
- file. This is not so optimal. So there is CachedInput class, which is returned
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 json is detected, ``Request.form``
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 paresed as input form by ``FieldStorage`` class.
827
- If input request does not have set one of these mime types, that form was not
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
- FieldStorage classes, when auto_args resp. auto_form is set. By default this
852
- property is set to ``0``. If it set to ``1``, blank values should be interpret
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
- FieldStorage classes. when auto_args resp. auto_form is set. By default this
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
- req.form = request.FieldStorage(
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 Request have get_options method too.
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 = None
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 req.config.items())
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
- As you can see, you can store your variables to request object. There are few
935
- reserved variables for you, which poorwsgi never use, and which are None by
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.config: for your config object
939
- :req.user: for user object, who is login
940
- :req.app\_: as prefix for any your application variable
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', fce=str)
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.request import FieldStorage # noqa
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
- form = FieldStorage(
147
- req, keep_blank_values=app.keep_blank_values,
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
- form = FieldStorage(
151
- req, keep_blank_values=app.keep_blank_values,
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
- bytes_read = form.bytes_read
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://%s:8080" % ADDRESS)
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, request, state # noqa
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
- req.form = request.FieldStorage(
104
- req,
105
- keep_blank_values=app.keep_blank_values,
106
- strict_parsing=app.strict_parsing,
107
- file_callback=factory.create)
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('E-Tag') == 'W/"0123"':
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={'E-Tag': 'W/"0123"'})
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(req, error):
535
+ def value_error_handler(*_):
521
536
  """ValueError exception handler example."""
522
- assert req
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={'E-Tag': etag})
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-error')
666
- def none_error_handler(_):
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."""