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.
- ayame-0.3/CHANGES.rst +37 -0
- ayame-0.3/LICENSE.txt +19 -0
- ayame-0.3/MANIFEST.in +6 -0
- ayame-0.3/PKG-INFO +114 -0
- ayame-0.3/README.rst +86 -0
- ayame-0.3/ayame/__init__.py +17 -0
- ayame-0.3/ayame/__version__.py +3 -0
- ayame-0.3/ayame/app/Ayame.properties +21 -0
- ayame-0.3/ayame/app.py +199 -0
- ayame-0.3/ayame/basic.py +122 -0
- ayame-0.3/ayame/border/FeedbackFieldBorder.html +13 -0
- ayame-0.3/ayame/border.py +122 -0
- ayame-0.3/ayame/converter.py +268 -0
- ayame-0.3/ayame/core.py +745 -0
- ayame-0.3/ayame/exception.py +86 -0
- ayame-0.3/ayame/form.py +513 -0
- ayame-0.3/ayame/http.py +226 -0
- ayame-0.3/ayame/i18n.py +170 -0
- ayame-0.3/ayame/link.py +90 -0
- ayame-0.3/ayame/local.py +53 -0
- ayame-0.3/ayame/markup.py +983 -0
- ayame-0.3/ayame/model.py +114 -0
- ayame-0.3/ayame/page/HTTPStatusPage.html +40 -0
- ayame-0.3/ayame/page.py +29 -0
- ayame-0.3/ayame/panel/FeedbackPanel.html +14 -0
- ayame-0.3/ayame/panel.py +74 -0
- ayame-0.3/ayame/res.py +127 -0
- ayame-0.3/ayame/route.py +474 -0
- ayame-0.3/ayame/session.py +33 -0
- ayame-0.3/ayame/uri.py +115 -0
- ayame-0.3/ayame/util.py +581 -0
- ayame-0.3/ayame/validator.py +179 -0
- ayame-0.3/ayame.egg-info/PKG-INFO +114 -0
- ayame-0.3/ayame.egg-info/SOURCES.txt +140 -0
- ayame-0.3/ayame.egg-info/dependency_links.txt +1 -0
- ayame-0.3/ayame.egg-info/requires.txt +2 -0
- ayame-0.3/ayame.egg-info/top_level.txt +1 -0
- ayame-0.3/doc/ayame.svg +468 -0
- ayame-0.3/pyproject.toml +76 -0
- ayame-0.3/setup.cfg +16 -0
- ayame-0.3/tests/base.py +190 -0
- ayame-0.3/tests/test_app/RedirectPage.html +10 -0
- ayame-0.3/tests/test_app/SimplePage.html +10 -0
- ayame-0.3/tests/test_app.py +340 -0
- ayame-0.3/tests/test_basic.py +266 -0
- ayame-0.3/tests/test_border/Bacon.html +15 -0
- ayame-0.3/tests/test_border/BaconBorder.html +20 -0
- ayame-0.3/tests/test_border/Beans.html +15 -0
- ayame-0.3/tests/test_border/BeansBorder.html +19 -0
- ayame-0.3/tests/test_border/Eggs.html +15 -0
- ayame-0.3/tests/test_border/EggsBorder.html +15 -0
- ayame-0.3/tests/test_border/HamBorder.html +23 -0
- ayame-0.3/tests/test_border/Lobster.html +15 -0
- ayame-0.3/tests/test_border/LobsterBorder.html +29 -0
- ayame-0.3/tests/test_border/Sausage.html +15 -0
- ayame-0.3/tests/test_border/SausageBorder.html +0 -0
- ayame-0.3/tests/test_border/ShallotsPage.html +20 -0
- ayame-0.3/tests/test_border/Spam.html +15 -0
- ayame-0.3/tests/test_border/SpamBorder.html +19 -0
- ayame-0.3/tests/test_border/Toast.html +15 -0
- ayame-0.3/tests/test_border/ToastBorder.html +19 -0
- ayame-0.3/tests/test_border/TomatoBorder.html +10 -0
- ayame-0.3/tests/test_border/TomatoBorder.properties +3 -0
- ayame-0.3/tests/test_border/TomatoBorder_ja.properties +3 -0
- ayame-0.3/tests/test_border/TomatoPage.html +10 -0
- ayame-0.3/tests/test_border/TomatoPage.properties +1 -0
- ayame-0.3/tests/test_border/TomatoPage_ja.properties +1 -0
- ayame-0.3/tests/test_border.py +712 -0
- ayame-0.3/tests/test_converter.py +388 -0
- ayame-0.3/tests/test_core/Aubergine.html +16 -0
- ayame-0.3/tests/test_core/Bacon.html +13 -0
- ayame-0.3/tests/test_core/BaconPage.html +14 -0
- ayame-0.3/tests/test_core/BaconPage.properties +1 -0
- ayame-0.3/tests/test_core/BaconPage_ja.properties +1 -0
- ayame-0.3/tests/test_core/Beans.html +13 -0
- ayame-0.3/tests/test_core/BeansPage.html +10 -0
- ayame-0.3/tests/test_core/BeansPage.properties +1 -0
- ayame-0.3/tests/test_core/BeansPage_ja.properties +1 -0
- ayame-0.3/tests/test_core/Eggs.html +18 -0
- ayame-0.3/tests/test_core/EggsPage.html +13 -0
- ayame-0.3/tests/test_core/Ham.html +17 -0
- ayame-0.3/tests/test_core/HamPage.html +8 -0
- ayame-0.3/tests/test_core/Lobster.html +0 -0
- ayame-0.3/tests/test_core/Sausage.html +15 -0
- ayame-0.3/tests/test_core/Shallots.html +14 -0
- ayame-0.3/tests/test_core/Spam.html +13 -0
- ayame-0.3/tests/test_core/SpamPage.html +10 -0
- ayame-0.3/tests/test_core/Toast.html +13 -0
- ayame-0.3/tests/test_core/ToastPage.NestedPage.html +12 -0
- ayame-0.3/tests/test_core/ToastPage.htm +12 -0
- ayame-0.3/tests/test_core/Tomato.html +3 -0
- ayame-0.3/tests/test_core.py +1718 -0
- ayame-0.3/tests/test_form/BeansPage.html +18 -0
- ayame-0.3/tests/test_form/EggsPage.html +18 -0
- ayame-0.3/tests/test_form/HamPage.html +18 -0
- ayame-0.3/tests/test_form/SpamPage.html +22 -0
- ayame-0.3/tests/test_form/ToastPage.html +18 -0
- ayame-0.3/tests/test_form.py +1404 -0
- ayame-0.3/tests/test_http.py +368 -0
- ayame-0.3/tests/test_i18n/Application.properties +1 -0
- ayame-0.3/tests/test_i18n/Component.properties +1 -0
- ayame-0.3/tests/test_i18n/MarkupContainer.properties +2 -0
- ayame-0.3/tests/test_i18n/Page.MarkupContainer.properties +2 -0
- ayame-0.3/tests/test_i18n/Page.properties +3 -0
- ayame-0.3/tests/test_i18n/i18n.txt +22 -0
- ayame-0.3/tests/test_i18n.py +167 -0
- ayame-0.3/tests/test_link/SpamPage.html +10 -0
- ayame-0.3/tests/test_link.py +173 -0
- ayame-0.3/tests/test_local.py +40 -0
- ayame-0.3/tests/test_markup.py +1234 -0
- ayame-0.3/tests/test_model.py +164 -0
- ayame-0.3/tests/test_page.py +39 -0
- ayame-0.3/tests/test_panel/Bacon.html +15 -0
- ayame-0.3/tests/test_panel/BaconPanel.html +18 -0
- ayame-0.3/tests/test_panel/Beans.html +15 -0
- ayame-0.3/tests/test_panel/BeansPanel.html +17 -0
- ayame-0.3/tests/test_panel/Eggs.html +15 -0
- ayame-0.3/tests/test_panel/EggsPanel.html +15 -0
- ayame-0.3/tests/test_panel/HamPanel.html +21 -0
- ayame-0.3/tests/test_panel/Lobster.html +15 -0
- ayame-0.3/tests/test_panel/LobsterPanel.html +23 -0
- ayame-0.3/tests/test_panel/Sausage.html +15 -0
- ayame-0.3/tests/test_panel/SausagePanel.html +0 -0
- ayame-0.3/tests/test_panel/ShallotsPage.html +18 -0
- ayame-0.3/tests/test_panel/Spam.html +15 -0
- ayame-0.3/tests/test_panel/SpamPanel.html +17 -0
- ayame-0.3/tests/test_panel/Toast.html +15 -0
- ayame-0.3/tests/test_panel/ToastPanel.html +17 -0
- ayame-0.3/tests/test_panel/TomatoPage.html +10 -0
- ayame-0.3/tests/test_panel/TomatoPanel.html +10 -0
- ayame-0.3/tests/test_panel/TomatoPanel.properties +1 -0
- ayame-0.3/tests/test_panel/TomatoPanel_ja.properties +1 -0
- ayame-0.3/tests/test_panel.py +645 -0
- ayame-0.3/tests/test_res/.txt +1 -0
- ayame-0.3/tests/test_res/Spam.txt +1 -0
- ayame-0.3/tests/test_res/ham.txt +1 -0
- ayame-0.3/tests/test_res.py +321 -0
- ayame-0.3/tests/test_route.py +413 -0
- ayame-0.3/tests/test_uri.py +323 -0
- ayame-0.3/tests/test_util.py +610 -0
- 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
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,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()
|
ayame-0.3/ayame/basic.py
ADDED
|
@@ -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>
|