ayame 0.3__py3-none-any.whl

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/__init__.py ADDED
@@ -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 *
ayame/__version__.py ADDED
@@ -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/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/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>
ayame/border.py ADDED
@@ -0,0 +1,122 @@
1
+ #
2
+ # ayame.border
3
+ #
4
+ # Copyright (c) 2011-2021 Akinori Hattori <hattya@gmail.com>
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+ #
8
+
9
+ from . import basic, core, form, markup
10
+ from .exception import RenderingError
11
+
12
+
13
+ __all__ = ['Border', 'FeedbackFieldBorder']
14
+
15
+
16
+ class Border(core.MarkupContainer):
17
+
18
+ def __init__(self, id, model=None):
19
+ super().__init__(id, model)
20
+ self.has_markup = True
21
+ self.render_body_only = True
22
+ # ayame:body element
23
+ self.body = _BorderBodyContainer(id)
24
+ self.add_to_border(self.body)
25
+ self.__body = False
26
+
27
+ def add_to_border(self, *args):
28
+ return super().add(*args)
29
+
30
+ def add(self, *args):
31
+ for o in args:
32
+ if isinstance(o, core._MessageContainer):
33
+ self.add_to_border(o)
34
+ else:
35
+ self.body.add(o)
36
+ return self
37
+
38
+ def on_render(self, element):
39
+ def step(element, depth):
40
+ return element.qname not in (markup.AYAME_BODY, markup.AYAME_HEAD)
41
+
42
+ # load markup for Panel
43
+ m = self.load_markup()
44
+ if m.root is None:
45
+ # markup is empty
46
+ return element
47
+
48
+ ayame_border = ayame_body = ayame_head = None
49
+ for elem, _ in m.root.walk(step=step):
50
+ if elem.qname == markup.AYAME_BORDER:
51
+ if ayame_border is None:
52
+ ayame_border = elem
53
+ elif elem.qname == markup.AYAME_BODY:
54
+ if (ayame_border is not None
55
+ and ayame_body is None):
56
+ # replace children of ayame:body element
57
+ ayame_body = elem
58
+ ayame_body.type = markup.Element.OPEN
59
+ ayame_body[:] = element
60
+ elif elem.qname == markup.AYAME_HEAD:
61
+ if ('html' in m.lang
62
+ and ayame_head is None):
63
+ ayame_head = elem
64
+ if ayame_border is None:
65
+ raise RenderingError(self, "'ayame:border' element is not found")
66
+ elif ayame_body is None:
67
+ raise RenderingError(self, "'ayame:body' element is not found")
68
+ # append ayame:head element to Page
69
+ if ayame_head is not None:
70
+ self.page().head.extend(ayame_head)
71
+ # render border
72
+ element[:] = ayame_border
73
+ return super().on_render(element)
74
+
75
+ def on_render_element(self, element):
76
+ if element.qname == markup.AYAME_BODY:
77
+ # skip duplicates
78
+ if not self.__body:
79
+ element.attrib[markup.AYAME_ID] = self.body.id
80
+ self.__body = True
81
+ return element
82
+ return super().on_render_element(element)
83
+
84
+
85
+ class _BorderBodyContainer(core.MarkupContainer):
86
+
87
+ def __init__(self, id, model=None):
88
+ super().__init__(id + '_body', model)
89
+ self.render_body_only = True
90
+
91
+ def on_render_element(self, element):
92
+ if element.qname == markup.AYAME_BODY:
93
+ return element
94
+ return super().on_render_element(element)
95
+
96
+ def tr(self, key, component=None):
97
+ # retrieve message from parent of Border
98
+ return super().tr(key, self.parent.parent)
99
+
100
+
101
+ class FeedbackFieldBorder(Border):
102
+
103
+ def __init__(self, id):
104
+ super().__init__(id)
105
+ self.render_body_only = False
106
+
107
+ self.__feedback = basic.Label('feedback', '')
108
+ self.add_to_border(self.__feedback)
109
+ self.add_to_border(self._ClassModifier('class', self.__feedback.model))
110
+
111
+ def on_configure(self):
112
+ for c, _ in self.walk():
113
+ if isinstance(c, form.FormComponent):
114
+ self.__feedback.model.object = c.error
115
+ self.__feedback.visible = bool(c.error)
116
+
117
+ class _ClassModifier(core.AttributeModifier):
118
+
119
+ def new_value(self, value, error):
120
+ if error:
121
+ return value + '-error' if value else 'error'
122
+ return value