ayame 0.3__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 (141) hide show
  1. ayame-0.3/CHANGES.rst +37 -0
  2. ayame-0.3/LICENSE.txt +19 -0
  3. ayame-0.3/MANIFEST.in +6 -0
  4. ayame-0.3/PKG-INFO +114 -0
  5. ayame-0.3/README.rst +86 -0
  6. ayame-0.3/ayame/__init__.py +17 -0
  7. ayame-0.3/ayame/__version__.py +3 -0
  8. ayame-0.3/ayame/app/Ayame.properties +21 -0
  9. ayame-0.3/ayame/app.py +199 -0
  10. ayame-0.3/ayame/basic.py +122 -0
  11. ayame-0.3/ayame/border/FeedbackFieldBorder.html +13 -0
  12. ayame-0.3/ayame/border.py +122 -0
  13. ayame-0.3/ayame/converter.py +268 -0
  14. ayame-0.3/ayame/core.py +745 -0
  15. ayame-0.3/ayame/exception.py +86 -0
  16. ayame-0.3/ayame/form.py +513 -0
  17. ayame-0.3/ayame/http.py +226 -0
  18. ayame-0.3/ayame/i18n.py +170 -0
  19. ayame-0.3/ayame/link.py +90 -0
  20. ayame-0.3/ayame/local.py +53 -0
  21. ayame-0.3/ayame/markup.py +983 -0
  22. ayame-0.3/ayame/model.py +114 -0
  23. ayame-0.3/ayame/page/HTTPStatusPage.html +40 -0
  24. ayame-0.3/ayame/page.py +29 -0
  25. ayame-0.3/ayame/panel/FeedbackPanel.html +14 -0
  26. ayame-0.3/ayame/panel.py +74 -0
  27. ayame-0.3/ayame/res.py +127 -0
  28. ayame-0.3/ayame/route.py +474 -0
  29. ayame-0.3/ayame/session.py +33 -0
  30. ayame-0.3/ayame/uri.py +115 -0
  31. ayame-0.3/ayame/util.py +581 -0
  32. ayame-0.3/ayame/validator.py +179 -0
  33. ayame-0.3/ayame.egg-info/PKG-INFO +114 -0
  34. ayame-0.3/ayame.egg-info/SOURCES.txt +140 -0
  35. ayame-0.3/ayame.egg-info/dependency_links.txt +1 -0
  36. ayame-0.3/ayame.egg-info/requires.txt +2 -0
  37. ayame-0.3/ayame.egg-info/top_level.txt +1 -0
  38. ayame-0.3/doc/ayame.svg +468 -0
  39. ayame-0.3/pyproject.toml +76 -0
  40. ayame-0.3/setup.cfg +16 -0
  41. ayame-0.3/tests/base.py +190 -0
  42. ayame-0.3/tests/test_app/RedirectPage.html +10 -0
  43. ayame-0.3/tests/test_app/SimplePage.html +10 -0
  44. ayame-0.3/tests/test_app.py +340 -0
  45. ayame-0.3/tests/test_basic.py +266 -0
  46. ayame-0.3/tests/test_border/Bacon.html +15 -0
  47. ayame-0.3/tests/test_border/BaconBorder.html +20 -0
  48. ayame-0.3/tests/test_border/Beans.html +15 -0
  49. ayame-0.3/tests/test_border/BeansBorder.html +19 -0
  50. ayame-0.3/tests/test_border/Eggs.html +15 -0
  51. ayame-0.3/tests/test_border/EggsBorder.html +15 -0
  52. ayame-0.3/tests/test_border/HamBorder.html +23 -0
  53. ayame-0.3/tests/test_border/Lobster.html +15 -0
  54. ayame-0.3/tests/test_border/LobsterBorder.html +29 -0
  55. ayame-0.3/tests/test_border/Sausage.html +15 -0
  56. ayame-0.3/tests/test_border/SausageBorder.html +0 -0
  57. ayame-0.3/tests/test_border/ShallotsPage.html +20 -0
  58. ayame-0.3/tests/test_border/Spam.html +15 -0
  59. ayame-0.3/tests/test_border/SpamBorder.html +19 -0
  60. ayame-0.3/tests/test_border/Toast.html +15 -0
  61. ayame-0.3/tests/test_border/ToastBorder.html +19 -0
  62. ayame-0.3/tests/test_border/TomatoBorder.html +10 -0
  63. ayame-0.3/tests/test_border/TomatoBorder.properties +3 -0
  64. ayame-0.3/tests/test_border/TomatoBorder_ja.properties +3 -0
  65. ayame-0.3/tests/test_border/TomatoPage.html +10 -0
  66. ayame-0.3/tests/test_border/TomatoPage.properties +1 -0
  67. ayame-0.3/tests/test_border/TomatoPage_ja.properties +1 -0
  68. ayame-0.3/tests/test_border.py +712 -0
  69. ayame-0.3/tests/test_converter.py +388 -0
  70. ayame-0.3/tests/test_core/Aubergine.html +16 -0
  71. ayame-0.3/tests/test_core/Bacon.html +13 -0
  72. ayame-0.3/tests/test_core/BaconPage.html +14 -0
  73. ayame-0.3/tests/test_core/BaconPage.properties +1 -0
  74. ayame-0.3/tests/test_core/BaconPage_ja.properties +1 -0
  75. ayame-0.3/tests/test_core/Beans.html +13 -0
  76. ayame-0.3/tests/test_core/BeansPage.html +10 -0
  77. ayame-0.3/tests/test_core/BeansPage.properties +1 -0
  78. ayame-0.3/tests/test_core/BeansPage_ja.properties +1 -0
  79. ayame-0.3/tests/test_core/Eggs.html +18 -0
  80. ayame-0.3/tests/test_core/EggsPage.html +13 -0
  81. ayame-0.3/tests/test_core/Ham.html +17 -0
  82. ayame-0.3/tests/test_core/HamPage.html +8 -0
  83. ayame-0.3/tests/test_core/Lobster.html +0 -0
  84. ayame-0.3/tests/test_core/Sausage.html +15 -0
  85. ayame-0.3/tests/test_core/Shallots.html +14 -0
  86. ayame-0.3/tests/test_core/Spam.html +13 -0
  87. ayame-0.3/tests/test_core/SpamPage.html +10 -0
  88. ayame-0.3/tests/test_core/Toast.html +13 -0
  89. ayame-0.3/tests/test_core/ToastPage.NestedPage.html +12 -0
  90. ayame-0.3/tests/test_core/ToastPage.htm +12 -0
  91. ayame-0.3/tests/test_core/Tomato.html +3 -0
  92. ayame-0.3/tests/test_core.py +1718 -0
  93. ayame-0.3/tests/test_form/BeansPage.html +18 -0
  94. ayame-0.3/tests/test_form/EggsPage.html +18 -0
  95. ayame-0.3/tests/test_form/HamPage.html +18 -0
  96. ayame-0.3/tests/test_form/SpamPage.html +22 -0
  97. ayame-0.3/tests/test_form/ToastPage.html +18 -0
  98. ayame-0.3/tests/test_form.py +1404 -0
  99. ayame-0.3/tests/test_http.py +368 -0
  100. ayame-0.3/tests/test_i18n/Application.properties +1 -0
  101. ayame-0.3/tests/test_i18n/Component.properties +1 -0
  102. ayame-0.3/tests/test_i18n/MarkupContainer.properties +2 -0
  103. ayame-0.3/tests/test_i18n/Page.MarkupContainer.properties +2 -0
  104. ayame-0.3/tests/test_i18n/Page.properties +3 -0
  105. ayame-0.3/tests/test_i18n/i18n.txt +22 -0
  106. ayame-0.3/tests/test_i18n.py +167 -0
  107. ayame-0.3/tests/test_link/SpamPage.html +10 -0
  108. ayame-0.3/tests/test_link.py +173 -0
  109. ayame-0.3/tests/test_local.py +40 -0
  110. ayame-0.3/tests/test_markup.py +1234 -0
  111. ayame-0.3/tests/test_model.py +164 -0
  112. ayame-0.3/tests/test_page.py +39 -0
  113. ayame-0.3/tests/test_panel/Bacon.html +15 -0
  114. ayame-0.3/tests/test_panel/BaconPanel.html +18 -0
  115. ayame-0.3/tests/test_panel/Beans.html +15 -0
  116. ayame-0.3/tests/test_panel/BeansPanel.html +17 -0
  117. ayame-0.3/tests/test_panel/Eggs.html +15 -0
  118. ayame-0.3/tests/test_panel/EggsPanel.html +15 -0
  119. ayame-0.3/tests/test_panel/HamPanel.html +21 -0
  120. ayame-0.3/tests/test_panel/Lobster.html +15 -0
  121. ayame-0.3/tests/test_panel/LobsterPanel.html +23 -0
  122. ayame-0.3/tests/test_panel/Sausage.html +15 -0
  123. ayame-0.3/tests/test_panel/SausagePanel.html +0 -0
  124. ayame-0.3/tests/test_panel/ShallotsPage.html +18 -0
  125. ayame-0.3/tests/test_panel/Spam.html +15 -0
  126. ayame-0.3/tests/test_panel/SpamPanel.html +17 -0
  127. ayame-0.3/tests/test_panel/Toast.html +15 -0
  128. ayame-0.3/tests/test_panel/ToastPanel.html +17 -0
  129. ayame-0.3/tests/test_panel/TomatoPage.html +10 -0
  130. ayame-0.3/tests/test_panel/TomatoPanel.html +10 -0
  131. ayame-0.3/tests/test_panel/TomatoPanel.properties +1 -0
  132. ayame-0.3/tests/test_panel/TomatoPanel_ja.properties +1 -0
  133. ayame-0.3/tests/test_panel.py +645 -0
  134. ayame-0.3/tests/test_res/.txt +1 -0
  135. ayame-0.3/tests/test_res/Spam.txt +1 -0
  136. ayame-0.3/tests/test_res/ham.txt +1 -0
  137. ayame-0.3/tests/test_res.py +321 -0
  138. ayame-0.3/tests/test_route.py +413 -0
  139. ayame-0.3/tests/test_uri.py +323 -0
  140. ayame-0.3/tests/test_util.py +610 -0
  141. ayame-0.3/tests/test_validator.py +254 -0
ayame-0.3/CHANGES.rst ADDED
@@ -0,0 +1,37 @@
1
+ Ayame Changelog
2
+ ===============
3
+
4
+ Version 0.3
5
+ -----------
6
+
7
+ Release date: 2024-03-31
8
+
9
+ * Support Python 3.10.
10
+ * Drop Python 3.6 support.
11
+ * Support Python 3.11.
12
+ * Drop Python 3.7 support.
13
+ * Support Python 3.12.
14
+
15
+
16
+ Version 0.2
17
+ -----------
18
+
19
+ Release date: 2021-09-22
20
+
21
+ * Support Python 3.5.
22
+ * Support Python 3.6.
23
+ * Drop Python 3.3 support.
24
+ * Support Python 3.7.
25
+ * Drop Python 3.4 support.
26
+ * Support Python 3.8.
27
+ * Drop Python 3.5 support.
28
+ * Support Python 3.9.
29
+ * Drop Python 2.7 support.
30
+
31
+
32
+ Version 0.1
33
+ -----------
34
+
35
+ Release date: 2015-02-02
36
+
37
+ * Initial release.
ayame-0.3/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011-2024 Akinori Hattori <hattya@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
ayame-0.3/MANIFEST.in ADDED
@@ -0,0 +1,6 @@
1
+ include CHANGES.rst
2
+ include MANIFEST.in
3
+ include README.rst
4
+ include pyproject.toml
5
+ recursive-include doc *.svg
6
+ recursive-include tests *.py *.html *.htm *.properties *.txt
ayame-0.3/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.1
2
+ Name: ayame
3
+ Version: 0.3
4
+ Summary: An Apache Wicket-like component based WSGI framework
5
+ Author-email: Akinori Hattori <hattya@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/hattya/ayame
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE.txt
26
+ Requires-Dist: Werkzeug
27
+ Requires-Dist: secure-cookie
28
+
29
+ Ayame
30
+ =====
31
+
32
+ Ayame is a component based WSGI framework. It is inspired by
33
+ `Apache Wicket`_, `Apache Click`_ and Flask_.
34
+
35
+ .. image:: https://img.shields.io/pypi/v/ayame.svg
36
+ :target: https://pypi.org/project/ayame
37
+
38
+ .. image:: https://github.com/hattya/ayame/actions/workflows/ci.yml/badge.svg
39
+ :target: https://github.com/hattya/ayame/actions/workflows/ci.yml
40
+
41
+ .. image:: https://ci.appveyor.com/api/projects/status/67nbqb4ej84liu9m?svg=true
42
+ :target: https://ci.appveyor.com/project/hattya/ayame
43
+
44
+ .. image:: https://codecov.io/gh/hattya/ayame/branch/master/graph/badge.svg
45
+ :target: https://codecov.io/gh/hattya/ayame
46
+
47
+ .. _Apache Wicket: https://wicket.apache.org/
48
+ .. _Apache Click: https://click.apache.org/
49
+ .. _Flask: https://palletsprojects.com/p/flask
50
+
51
+
52
+ Requirements
53
+ ------------
54
+
55
+ - Python 3.8+
56
+ - Werkzeug
57
+ - secure-cookie
58
+
59
+
60
+ Example Application
61
+ -------------------
62
+
63
+ ::
64
+
65
+ app.wsgi
66
+ app/
67
+ HelloWorld.html
68
+
69
+
70
+ app.wsgi
71
+ ~~~~~~~~
72
+
73
+ .. code:: python
74
+
75
+ from ayame import Ayame, Page
76
+ from ayame.basic import Label
77
+
78
+
79
+ class HelloWorld(Page):
80
+
81
+ def __init__(self):
82
+ super().__init__()
83
+ self.add(Label('message', 'Hello World!'))
84
+
85
+
86
+ application = Ayame(__name__)
87
+
88
+ map = application.config['ayame.route.map']
89
+ map.connect('/', HelloWorld)
90
+
91
+
92
+ HelloWorld.html
93
+ ~~~~~~~~~~~~~~~
94
+
95
+ .. code:: html
96
+
97
+ <?xml version="1.0"?>
98
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
99
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
100
+ <html xmlns="http://www.w3.org/1999/xhtml"
101
+ xmlns:ayame="http://hattya.github.io/ayame">
102
+ <head>
103
+ <title>HelloWorld</title>
104
+ </head>
105
+ <body>
106
+ <p ayame:id="message">...</p>
107
+ </body>
108
+ </html>
109
+
110
+
111
+ License
112
+ -------
113
+
114
+ Ayame is distributed under the terms of the MIT License.
ayame-0.3/README.rst ADDED
@@ -0,0 +1,86 @@
1
+ Ayame
2
+ =====
3
+
4
+ Ayame is a component based WSGI framework. It is inspired by
5
+ `Apache Wicket`_, `Apache Click`_ and Flask_.
6
+
7
+ .. image:: https://img.shields.io/pypi/v/ayame.svg
8
+ :target: https://pypi.org/project/ayame
9
+
10
+ .. image:: https://github.com/hattya/ayame/actions/workflows/ci.yml/badge.svg
11
+ :target: https://github.com/hattya/ayame/actions/workflows/ci.yml
12
+
13
+ .. image:: https://ci.appveyor.com/api/projects/status/67nbqb4ej84liu9m?svg=true
14
+ :target: https://ci.appveyor.com/project/hattya/ayame
15
+
16
+ .. image:: https://codecov.io/gh/hattya/ayame/branch/master/graph/badge.svg
17
+ :target: https://codecov.io/gh/hattya/ayame
18
+
19
+ .. _Apache Wicket: https://wicket.apache.org/
20
+ .. _Apache Click: https://click.apache.org/
21
+ .. _Flask: https://palletsprojects.com/p/flask
22
+
23
+
24
+ Requirements
25
+ ------------
26
+
27
+ - Python 3.8+
28
+ - Werkzeug
29
+ - secure-cookie
30
+
31
+
32
+ Example Application
33
+ -------------------
34
+
35
+ ::
36
+
37
+ app.wsgi
38
+ app/
39
+ HelloWorld.html
40
+
41
+
42
+ app.wsgi
43
+ ~~~~~~~~
44
+
45
+ .. code:: python
46
+
47
+ from ayame import Ayame, Page
48
+ from ayame.basic import Label
49
+
50
+
51
+ class HelloWorld(Page):
52
+
53
+ def __init__(self):
54
+ super().__init__()
55
+ self.add(Label('message', 'Hello World!'))
56
+
57
+
58
+ application = Ayame(__name__)
59
+
60
+ map = application.config['ayame.route.map']
61
+ map.connect('/', HelloWorld)
62
+
63
+
64
+ HelloWorld.html
65
+ ~~~~~~~~~~~~~~~
66
+
67
+ .. code:: html
68
+
69
+ <?xml version="1.0"?>
70
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
71
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
72
+ <html xmlns="http://www.w3.org/1999/xhtml"
73
+ xmlns:ayame="http://hattya.github.io/ayame">
74
+ <head>
75
+ <title>HelloWorld</title>
76
+ </head>
77
+ <body>
78
+ <p ayame:id="message">...</p>
79
+ </body>
80
+ </html>
81
+
82
+
83
+ License
84
+ -------
85
+
86
+ Ayame is distributed under the terms of the MIT License.
@@ -0,0 +1,17 @@
1
+ #
2
+ # ayame
3
+ #
4
+ # Copyright (c) 2011-2021 Akinori Hattori <hattya@gmail.com>
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ #
8
+
9
+ __author__ = 'Akinori Hattori <hattya@gmail.com>'
10
+ try:
11
+ from .__version__ import version as __version__
12
+ except ImportError:
13
+ __version__ = 'unknown'
14
+
15
+ from .app import *
16
+ from .core import *
17
+ from .exception import *
@@ -0,0 +1,3 @@
1
+ # file generated by scmver; DO NOT EDIT.
2
+
3
+ version = '0.3'
@@ -0,0 +1,21 @@
1
+ Required = '{label}' is required
2
+ Converter = The value of '{label}' is not a valid type '{type}'
3
+
4
+ Choice.single = The value of '{label}' is not a valid choice
5
+ Choice.multiple = The values of '{label}' contain invalid choices
6
+
7
+ RangeValidator.type = The value of '{label}' cannot validate
8
+ RangeValidator.minimum = The value of '{label}' must be at least {min}
9
+ RangeValidator.maximum = The value of '{label}' must be at most {max}
10
+ RangeValidator.range = The value of '{label}' must be between {min} and {max}
11
+ RangeValidator.exact = The value of '{label}' must be exactly {exact}
12
+
13
+ StringValidator.type = The value of '{label}' cannot validate
14
+ StringValidator.minimum = The value of '{label}' must be at least {min} characters long
15
+ StringValidator.maximum = The value of '{label}' must be at most {max} characters long
16
+ StringValidator.range = The value of '{label}' must be between {min} and {max} characters long
17
+ StringValidator.exact = The value of '{label}' must be exactly {exact} characters long
18
+
19
+ RegexValidator = The value of '{label}' does not match pattern '{pattern}'
20
+ EmailValidator = The value of '{label}' is not a valid email address
21
+ URLValidator = The value of '{label}' is not a valid URL
ayame-0.3/ayame/app.py ADDED
@@ -0,0 +1,199 @@
1
+ #
2
+ # ayame.app
3
+ #
4
+ # Copyright (c) 2011-2024 Akinori Hattori <hattya@gmail.com>
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ #
8
+
9
+ import locale
10
+ import os
11
+ import sys
12
+
13
+ import werkzeug.datastructures
14
+
15
+ from . import (converter, core, http, i18n, local, markup, page, res, route,
16
+ session, uri, util)
17
+ from .exception import AyameError, _Redirect
18
+
19
+
20
+ __all__ = ['Ayame', 'Request']
21
+
22
+
23
+ class Ayame:
24
+
25
+ def __init__(self, name):
26
+ self._name = name
27
+ try:
28
+ self._root = os.path.abspath(os.path.dirname(sys.modules[name].__file__))
29
+ except (AttributeError, KeyError):
30
+ self._root = os.getcwd()
31
+ session_dir = os.path.join(self._root, 'session')
32
+ self.config = {
33
+ 'ayame.converter.registry': converter.ConverterRegistry(),
34
+ 'ayame.i18n.cache': util.LRUCache(64),
35
+ 'ayame.i18n.localizer': i18n.Localizer(),
36
+ 'ayame.markup.cache': util.LRUCache(64),
37
+ 'ayame.markup.encoding': 'utf-8',
38
+ 'ayame.markup.loader': markup.MarkupLoader,
39
+ 'ayame.markup.pretty': False,
40
+ 'ayame.markup.renderer': markup.MarkupRenderer,
41
+ 'ayame.markup.separator': '.',
42
+ 'ayame.max.redirect': 7,
43
+ 'ayame.page.http': page.HTTPStatusPage,
44
+ 'ayame.request': Request,
45
+ 'ayame.resource.loader': res.ResourceLoader(),
46
+ 'ayame.route.map': route.Map(),
47
+ 'ayame.session.store': session.FileSystemSessionStore(session_dir, 'ayame_%s.sess'),
48
+ 'ayame.session.name': 'session_id',
49
+ 'ayame.session.expires': None,
50
+ 'ayame.session.max_age': None,
51
+ 'ayame.session.domain': None,
52
+ 'ayame.session.path': '/',
53
+ 'ayame.session.secure': False,
54
+ 'ayame.session.httponly': True,
55
+ }
56
+
57
+ @property
58
+ def context(self):
59
+ return local.context()
60
+
61
+ @property
62
+ def environ(self):
63
+ return self.context.environ
64
+
65
+ @property
66
+ def request(self):
67
+ return self.context.request
68
+
69
+ @property
70
+ def session(self):
71
+ return self.context.session
72
+
73
+ @property
74
+ def _router(self):
75
+ return self.context._router
76
+
77
+ def __call__(self, environ, start_response):
78
+ try:
79
+ ctx = local.push(self, environ)
80
+ ctx._router = self.config['ayame.route.map'].bind(environ)
81
+ # dispatch
82
+ o, values = ctx._router.match()
83
+ ctx.request = self.config['ayame.request'](environ, values)
84
+ ctx.session = session.get(self, environ)
85
+ for _ in range(self.config['ayame.max.redirect']):
86
+ try:
87
+ status, headers, content = self.handle_request(o)
88
+ except _Redirect as r:
89
+ if r.args[3] == _Redirect.PERMANENT:
90
+ raise http.MovedPermanently(uri.application_uri(environ)
91
+ + self.uri_for(*r.args[:3], relative=True)[1:])
92
+ elif r.args[3] != _Redirect.INTERNAL:
93
+ raise http.Found(uri.application_uri(environ)
94
+ + self.uri_for(*r.args[:3], relative=True)[1:])
95
+ o = r.args[0]
96
+ ctx.request.path = None
97
+ continue
98
+ break
99
+ else:
100
+ raise AyameError('reached to the maximum number of internal redirects')
101
+ exc_info = None
102
+ set_cookie = session.save(self, ctx.session)
103
+ if set_cookie:
104
+ headers.append(set_cookie)
105
+ except Exception as e:
106
+ status, headers, exc_info, content = self.handle_error(e)
107
+ finally:
108
+ ctx = local.pop()
109
+ if ctx.request is not None:
110
+ ctx.request.close()
111
+
112
+ start_response(status, headers, exc_info)
113
+ return content
114
+
115
+ def handle_request(self, object):
116
+ if isinstance(object, type):
117
+ if issubclass(object, core.Page):
118
+ object = object()
119
+ else:
120
+ # type is callable, so it might cause unexpected error
121
+ object = None
122
+ if callable(object):
123
+ return object()
124
+ raise http.NotFound(uri.request_path(self.environ))
125
+
126
+ def handle_error(self, error):
127
+ if isinstance(error, http.HTTPStatus):
128
+ page = self.config['ayame.page.http'](error)
129
+ status, headers, content = page()
130
+ exc_info = None
131
+ else:
132
+ status, headers, content = http.InternalServerError.status, [], []
133
+ exc_info = sys.exc_info()
134
+ return status, headers, exc_info, content
135
+
136
+ def forward(self, object, values=None, anchor=None):
137
+ raise _Redirect(object, values, anchor, _Redirect.INTERNAL)
138
+
139
+ def redirect(self, object, values=None, anchor=None, permanent=False):
140
+ raise _Redirect(object, values, anchor, _Redirect.PERMANENT if permanent else _Redirect.TEMPORARY)
141
+
142
+ def uri_for(self, *args, **kwargs):
143
+ return self._router.build(*args, **kwargs)
144
+
145
+
146
+ class Request:
147
+
148
+ __slots__ = ('environ', 'method', 'uri', 'query', 'form_data', 'path',
149
+ 'locale')
150
+
151
+ def __init__(self, environ, values):
152
+ self.environ = environ
153
+ self.method = environ['REQUEST_METHOD']
154
+ self.uri = values
155
+ self.query = uri.parse_qs(environ)
156
+ self.form_data = http.parse_form_data(environ)
157
+ # retrieve ayame:path
158
+ if self.method == 'GET':
159
+ self.path = self.query.get(core.AYAME_PATH)
160
+ elif self.method == 'POST':
161
+ self.path = self.form_data.get(core.AYAME_PATH)
162
+ else:
163
+ self.path = None
164
+ if self.path:
165
+ self.path = self.path[0]
166
+ self.locale = self._parse_locales(environ)
167
+
168
+ def _parse_locales(self, environ):
169
+ values = http.parse_accept(environ.get('HTTP_ACCEPT_LANGUAGE'))
170
+ if values:
171
+ v = values[0][0]
172
+ sep = '-'
173
+ else:
174
+ v = locale.getlocale()[0]
175
+ sep = '_'
176
+ if v:
177
+ v = v.split(sep, 1)
178
+ return (v[0].lower(), v[1].upper() if len(v) > 1 else None)
179
+ return (None,) * 2
180
+
181
+ @property
182
+ def input(self):
183
+ return self.environ['wsgi.input']
184
+
185
+ @property
186
+ def session(self):
187
+ return local.context().session
188
+
189
+ def __enter__(self):
190
+ return self
191
+
192
+ def __exit__(self, *exc_info):
193
+ self.close()
194
+
195
+ def close(self):
196
+ for _, data in self.form_data.items():
197
+ for v in data:
198
+ if isinstance(v, werkzeug.datastructures.FileStorage):
199
+ v.close()
@@ -0,0 +1,122 @@
1
+ #
2
+ # ayame.basic
3
+ #
4
+ # Copyright (c) 2011-2021 Akinori Hattori <hattya@gmail.com>
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ #
8
+
9
+ import collections.abc
10
+
11
+ from . import core, markup, uri
12
+ from . import model as mm
13
+
14
+
15
+ __all__ = ['Label', 'ListView', 'PropertyListView', 'ContextPathGenerator',
16
+ 'ContextImage', 'ContextLink']
17
+
18
+
19
+ class Label(core.Component):
20
+
21
+ def __init__(self, id, model=None):
22
+ if isinstance(model, str):
23
+ model = mm.Model(model)
24
+ super().__init__(id, model)
25
+
26
+ def on_render(self, element):
27
+ element[:] = (self.model_object_as_string(),)
28
+ return element
29
+
30
+
31
+ class ListView(core.MarkupContainer):
32
+
33
+ def __init__(self, id, model=None, populate_item=None):
34
+ if isinstance(model, collections.abc.Sequence):
35
+ model = mm.Model(model)
36
+ super().__init__(id, model)
37
+ self._populate_item = populate_item
38
+
39
+ def on_before_render(self):
40
+ o = self.model_object
41
+ if o is not None:
42
+ for i in range(len(o)):
43
+ li = self.new_item(i)
44
+ self.add(li)
45
+ self.populate_item(li)
46
+ super().on_before_render()
47
+
48
+ def on_render(self, element):
49
+ skel = element.copy()
50
+ skel.qname = markup.DIV
51
+ del element[:]
52
+ for c in self.children:
53
+ element.extend(c.on_render(skel.copy()))
54
+ return element
55
+
56
+ def populate_item(self, item):
57
+ if callable(self._populate_item):
58
+ return self._populate_item(item)
59
+
60
+ def new_item(self, index):
61
+ return _ListItem(index, self.new_model(index))
62
+
63
+ def new_model(self, index):
64
+ return _ListItemModel(self, index)
65
+
66
+
67
+ class _ListItem(core.MarkupContainer):
68
+
69
+ def __init__(self, index, model):
70
+ super().__init__(str(index), model)
71
+ self.__index = index
72
+
73
+ @property
74
+ def index(self):
75
+ return self.__index
76
+
77
+
78
+ class _ListItemModel(mm.Model):
79
+
80
+ def __init__(self, list_view, index):
81
+ self.__list_view = list_view
82
+ self.__index = index
83
+
84
+ def object():
85
+ def fget(self):
86
+ return self.__list_view.model_object[self.__index]
87
+
88
+ def fset(self, object):
89
+ self.__list_view.model_object[self.__index] = object
90
+
91
+ return locals()
92
+
93
+ object = property(**object())
94
+
95
+
96
+ class PropertyListView(ListView):
97
+
98
+ def new_model(self, index):
99
+ return mm.CompoundModel(super().new_model(index))
100
+
101
+
102
+ class ContextPathGenerator(core.AttributeModifier):
103
+
104
+ def __init__(self, attr, rel_path):
105
+ super().__init__(attr, mm.Model(rel_path))
106
+
107
+ def new_value(self, value, new_value):
108
+ return uri.relative_uri(self.environ, new_value)
109
+
110
+
111
+ class ContextImage(core.Component):
112
+
113
+ def __init__(self, id, rel_path):
114
+ super().__init__(id)
115
+ self.add(ContextPathGenerator('src', rel_path))
116
+
117
+
118
+ class ContextLink(core.Component):
119
+
120
+ def __init__(self, id, rel_path):
121
+ super().__init__(id)
122
+ self.add(ContextPathGenerator('href', rel_path))
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ayame="http://hattya.github.io/ayame">
4
+ <head>
5
+ <title>FeedbackFieldBorder</title>
6
+ </head>
7
+ <body>
8
+ <ayame:border>
9
+ <ayame:body />
10
+ <p ayame:id="feedback" class="feedback">message</p>
11
+ </ayame:border>
12
+ </body>
13
+ </html>