PoorWSGI 2.6.1__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 (62) hide show
  1. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PKG-INFO +1 -1
  2. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PoorWSGI.egg-info/PKG-INFO +1 -1
  3. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PoorWSGI.egg-info/SOURCES.txt +1 -4
  4. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/ChangeLog +23 -0
  5. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_footer.html +1 -1
  6. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_poorwsgi_api.html +3 -1
  7. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/documentation.rst +99 -55
  8. poorwsgi-2.7.0/examples/http_digest.py +200 -0
  9. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/large_file.py +22 -15
  10. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/metrics.py +1 -0
  11. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/openapi3.py +2 -2
  12. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/put_file.py +6 -4
  13. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/simple.py +196 -134
  14. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/simple_json.py +15 -3
  15. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/websocket.py +10 -2
  16. poorwsgi-2.7.0/poorwsgi/fieldstorage.py +790 -0
  17. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/headers.py +39 -1
  18. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/openapi_wrapper.py +1 -1
  19. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/request.py +129 -294
  20. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/response.py +108 -14
  21. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/results.py +44 -36
  22. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/session.py +1 -3
  23. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/state.py +2 -2
  24. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/wsgi.py +7 -8
  25. poorwsgi-2.7.0/setup.py +206 -0
  26. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/tests/test_digest.py +17 -8
  27. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/tests/test_header.py +4 -2
  28. poorwsgi-2.7.0/tests/test_request.py +353 -0
  29. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/tests/test_responses.py +61 -9
  30. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/tests/test_session.py +4 -0
  31. PoorWSGI-2.6.1/examples/async.py +0 -36
  32. PoorWSGI-2.6.1/examples/encoding.py +0 -54
  33. PoorWSGI-2.6.1/examples/http_digest.py +0 -237
  34. PoorWSGI-2.6.1/setup.py +0 -229
  35. PoorWSGI-2.6.1/test_hidden.py +0 -58
  36. PoorWSGI-2.6.1/tests/test_request.py +0 -210
  37. PoorWSGI-2.6.1/websockets_chat_async.py +0 -95
  38. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/CONTRIBUTION.rst +0 -0
  39. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/MANIFEST.in +0 -0
  40. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PoorWSGI.egg-info/dependency_links.txt +0 -0
  41. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PoorWSGI.egg-info/requires.txt +0 -0
  42. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/PoorWSGI.egg-info/top_level.txt +0 -0
  43. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/README.rst +0 -0
  44. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_documentation.html +0 -0
  45. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_header.html +0 -0
  46. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_install.html +0 -0
  47. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_licence.html +0 -0
  48. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_poorwsgi.html +0 -0
  49. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_reference.html +0 -0
  50. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/_text.html +0 -0
  51. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/about.rst +0 -0
  52. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/install.rst +0 -0
  53. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/licence.txt +0 -0
  54. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/small-logo.png +0 -0
  55. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/style.css +0 -0
  56. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/doc/web.css +0 -0
  57. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/openapi.json +0 -0
  58. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/examples/test.digest +0 -0
  59. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/__init__.py +0 -0
  60. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/digest.py +0 -0
  61. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/poorwsgi/py.typed +0 -0
  62. {PoorWSGI-2.6.1 → poorwsgi-2.7.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PoorWSGI
3
- Version: 2.6.1
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.1
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
@@ -2,8 +2,6 @@ CONTRIBUTION.rst
2
2
  MANIFEST.in
3
3
  README.rst
4
4
  setup.py
5
- test_hidden.py
6
- websockets_chat_async.py
7
5
  PoorWSGI.egg-info/PKG-INFO
8
6
  PoorWSGI.egg-info/SOURCES.txt
9
7
  PoorWSGI.egg-info/dependency_links.txt
@@ -26,8 +24,6 @@ doc/licence.txt
26
24
  doc/small-logo.png
27
25
  doc/style.css
28
26
  doc/web.css
29
- examples/async.py
30
- examples/encoding.py
31
27
  examples/http_digest.py
32
28
  examples/large_file.py
33
29
  examples/metrics.py
@@ -40,6 +36,7 @@ examples/test.digest
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,26 @@
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
+
19
+ ==== 2.6.3 ====
20
+ * HTTPException has status_code property
21
+ * Implicit value for not set cookie is None or empty dict for OpenAPI
22
+ * Fix Content-Length header when partial Response
23
+
1
24
  ==== 2.6.1 ====
2
25
  * Fix OpenAPI Core wrappers
3
26
 
@@ -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')
@@ -0,0 +1,200 @@
1
+ """HTTP WWW-Authenticate Digest Example."""
2
+ import logging
3
+ from hashlib import sha256
4
+ from os import path
5
+ from sys import path as python_path
6
+ from time import time
7
+ from wsgiref.simple_server import make_server
8
+
9
+ python_path.insert(
10
+ 0, path.abspath(path.join(path.dirname(__file__), path.pardir)))
11
+
12
+ # pylint: disable=wrong-import-position
13
+ from poorwsgi import Application, state # noqa
14
+ from poorwsgi.digest import PasswordMap, check_digest, hexdigest # noqa
15
+ from poorwsgi.response import EmptyResponse, redirect # noqa
16
+
17
+ FILE = path.join(path.dirname(__file__), 'test.digest')
18
+
19
+ logging.getLogger().setLevel("DEBUG")
20
+ app = application = Application(__name__) # pylint: disable=invalid-name
21
+ # application = app
22
+ app.debug = True
23
+ app.secret_key = sha256(str(time()).encode()).hexdigest()
24
+ # app.auth_algorithm = 'SHA-256-sess'
25
+ app.auth_type = 'Digest'
26
+ app.auth_timeout = 60
27
+
28
+ ADMIN = 'Admin Zone'
29
+ USER = 'User Zone'
30
+ # user/looser, foo/bar, admin/admin, Ondřej/heslíčko
31
+ app.auth_map = PasswordMap(FILE)
32
+ app.auth_map.load()
33
+
34
+ # pylint: disable=unused-argument
35
+
36
+
37
+ def get_header(title):
38
+ """Return HTML header list of lines."""
39
+ return (
40
+ "<html>", "<head>",
41
+ '<meta http-equiv="content-type" content="text/html; charset=utf-8"/>',
42
+ f"<title>{__file__} - {title}</title>", "</head>", "<body>",
43
+ f"<h1>{__file__} - {title}</h1>")
44
+
45
+
46
+ def get_footer():
47
+ """Return HTML footer list of lines."""
48
+ return ("<hr>", "<small>Copyright (c) 2020 Ondřej Tůma. See ",
49
+ '<a href="http://poorhttp.zeropage.cz">poorhttp.zeropage.cz</a>'
50
+ '</small>.', "</body>", "</html>")
51
+
52
+
53
+ def get_link(href, text=None, title=None):
54
+ """Return HTML anchor."""
55
+ text = text or title or href
56
+ title = title or text
57
+ return f'<a href="{href}" title="{title}">{text}</a>'
58
+
59
+
60
+ @app.route('/')
61
+ def root(req):
62
+ """Return Root (Index) page."""
63
+ body = ('<ul>', "<li>" + get_link('/admin_zone') +
64
+ " - admin zone (admin/admin)</li>", "<li>" +
65
+ get_link('/user_zone') + " - user zone (user/looser;sha/sha)</li>",
66
+ "<li>" + get_link('/user') + " - user (user/looser)</li>", "<li>" +
67
+ get_link('/user/utf-8') + " - utf-8 (Ondřej/heslíčko)</li>",
68
+ "<li>" + get_link('/foo') + " - foo (foo/bar)</li>",
69
+ "<li>" + get_link('/unknown') + " - unknown</li>",
70
+ "<li>" + get_link('/spaces in url') + " - spaces in url</li>",
71
+ "<li>" + get_link('/čeština v url') + " - diacitics in url</li>",
72
+ "<li>" + get_link('/crazy in url 🤪') + " - unicode in url</li>",
73
+ '</ul>')
74
+
75
+ for line in get_header("Root") + body + get_footer():
76
+ yield line.encode() + b'\n'
77
+
78
+
79
+ @app.route('/admin_zone')
80
+ @check_digest(ADMIN)
81
+ def admin_zone(req):
82
+ """Page only for ADMIN realm."""
83
+ body = (f'<h2>{ADMIN} test for {app.auth_algorithm} algorithm.</h2>',
84
+ '<ul>', '<li>' + get_link('/', 'Root') + '</li>',
85
+ '<li>' + get_link('/admin_zone?arg=42', 'one more time') + '</li>',
86
+ '</ul>')
87
+
88
+ for line in get_header("Root") + body + get_footer():
89
+ yield line.encode() + b'\n'
90
+
91
+
92
+ @app.route('/user_zone')
93
+ @check_digest(USER)
94
+ def user_zone(req):
95
+ """Page for USER realm."""
96
+ body = (f'<h2>{USER} test for {app.auth_algorithm} algorithm.</h2>',
97
+ f'User: {req.user}', '<ul>',
98
+ '<li>' + get_link('/', 'Root') + '</li>', '<li>' +
99
+ get_link('/user_zone?param=text', 'one more time') + '</li>',
100
+ '</ul>')
101
+
102
+ for line in get_header("Root") + body + get_footer():
103
+ yield line.encode() + b'\n'
104
+
105
+
106
+ @app.route('/user')
107
+ @check_digest(USER, 'user')
108
+ def user_only(req):
109
+ """Page for user only."""
110
+ body = (f'<h2>User test for {app.auth_algorithm} algorithm.</h2>',
111
+ f'User: {req.user}', '<ul>',
112
+ '<li>' + get_link('/', 'Root') + '</li>', '<li>' +
113
+ get_link('/user?param=1234', 'one more time') + '</li>', '</ul>')
114
+
115
+ for line in get_header("Root") + body + get_footer():
116
+ yield line.encode() + b'\n'
117
+
118
+
119
+ @app.route('/foo')
120
+ @check_digest(USER, 'foo')
121
+ def foo_only(req):
122
+ """Page for foo user only."""
123
+ body = (f'<h2>Foo test for {app.auth_algorithm} algorithm.</h2>',
124
+ f'User: {req.user}', '<ul>', '<li>' + get_link('/', 'Root') +
125
+ '</li>', '</ul>', '<form method="post" action="/foo/passwd">',
126
+ '<label>Enter new password:',
127
+ '<input type="password" name="password"/>', '</label>',
128
+ '<button type="submit">Change Password</button>', '</form>')
129
+
130
+ for line in get_header("Root") + body + get_footer():
131
+ yield line.encode() + b'\n'
132
+
133
+
134
+ @app.route('/user/utf-8')
135
+ @check_digest(USER, 'Ondřej')
136
+ def utf8_chars(req):
137
+ """Page for user only."""
138
+ body = (f'<h2>User test for {app.auth_algorithm} algorithm.</h2>',
139
+ f'User: {req.user}', '<ul>',
140
+ '<li>' + get_link('/', 'Root') + '</li>', '<li>' +
141
+ get_link('/user/utf-8?param=1234', 'one more time') + '</li>',
142
+ '</ul>')
143
+
144
+ for line in get_header("Root") + body + get_footer():
145
+ yield line.encode() + b'\n'
146
+
147
+
148
+ @app.route('/foo/passwd', method=state.METHOD_POST)
149
+ @check_digest(USER, 'foo')
150
+ def foo_password(req):
151
+ """Change foo's password."""
152
+ digest = hexdigest(req.user, USER, req.form.get('password'), app.auth_hash)
153
+ app.auth_map.set(USER, req.user, digest)
154
+ redirect('/foo')
155
+
156
+
157
+ @app.route('/unknown')
158
+ @check_digest(USER, 'unknown')
159
+ def unknown_endpoint(req):
160
+ """Page for digest test."""
161
+ return EmptyResponse()
162
+
163
+
164
+ def generic_response(url, user):
165
+ """Return generic response"""
166
+ body = (f'<h2>{USER} test for {app.auth_algorithm} algorithm.</h2>',
167
+ f'User: {user}', '<ul>', '<li>' + get_link('/', 'Root') + '</li>',
168
+ '<li>' + get_link(url + '?param=text', 'one more time') + '</li>',
169
+ '</ul>')
170
+
171
+ for line in get_header("Root") + body + get_footer():
172
+ yield line.encode() + b'\n'
173
+
174
+
175
+ @app.route('/spaces in url')
176
+ @check_digest(USER)
177
+ def spaces_in_url(req):
178
+ """URL with spaces in path."""
179
+ return generic_response(req.path, req.user)
180
+
181
+
182
+ @app.route('/čeština v url')
183
+ @check_digest(USER)
184
+ def diacritics_in_url(req):
185
+ """URL with diacritics in path."""
186
+ return generic_response(req.path, req.user)
187
+
188
+
189
+ @app.route('/crazy in url 🤪')
190
+ @check_digest(USER)
191
+ def crazy_in_url(req):
192
+ """URL with unicode in path."""
193
+ return generic_response(req.path, req.user)
194
+
195
+
196
+ if __name__ == '__main__':
197
+ # pylint: disable=invalid-name
198
+ httpd = make_server('127.0.0.1', 8080, app)
199
+ logging.info("Starting to serve on http://127.0.0.1:8080")
200
+ httpd.serve_forever()