Pyjoyment 0.1.0__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.
Files changed (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,134 @@
1
+ """
2
+ Pyjoyment.Plugin.EPLRenderer - Embedded Python Lite renderer plugin
3
+ ===================================================================
4
+ ::
5
+
6
+ # Pyjoyment
7
+ app.plugin('EPLRenderer')
8
+
9
+ # Pyjoyment.Lite
10
+ plugin('EPLRenderer')
11
+
12
+ :mod:`Pyjoyment.Plugin.EPLRenderer` is a renderer for ``epl`` templates,
13
+ which are pretty much just raw :mod:`Pyjo.Template`. The template gets the
14
+ controller as its first argument. ::
15
+
16
+ % c = args[0]
17
+ Hello <%= c.param('name') %>!
18
+
19
+ This is a core plugin, that means it is always enabled and its code a good
20
+ example for learning to build new plugins, you're welcome to fork it.
21
+
22
+ See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
23
+ default.
24
+
25
+ See also
26
+ --------
27
+
28
+ :mod:`Pyjoyment`, https://mojolicious.org.
29
+
30
+ Ported from `Mojolicious::Plugin::EPLRenderer
31
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/EPLRenderer.pm>`_ of Mojolicious 9.49.
32
+ """
33
+
34
+ import Pyjo.Exception
35
+ import Pyjo.Template
36
+ import Pyjoyment.Plugin
37
+
38
+ from Pyjo.Util import encode, md5_sum
39
+
40
+ import io
41
+
42
+ from typing import TYPE_CHECKING, Any
43
+
44
+
45
+ if TYPE_CHECKING:
46
+ import Pyjoyment
47
+ import Pyjoyment.Controller
48
+ import Pyjoyment.Renderer
49
+
50
+
51
+ class Pyjoyment_Plugin_EPLRenderer(Pyjoyment.Plugin.object):
52
+ """
53
+ :mod:`Pyjoyment.Plugin.EPLRenderer` inherits all methods from
54
+ :mod:`Pyjoyment.Plugin` and implements the following new ones.
55
+ """
56
+
57
+ def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
58
+ """::
59
+
60
+ plugin.register(Pyjoyment.new())
61
+
62
+ Register renderer in :mod:`Pyjoyment` application.
63
+ """
64
+ app.renderer.add_handler('epl', _handler)
65
+
66
+
67
+ def _handler(
68
+ renderer: 'Pyjoyment.Renderer.object',
69
+ c: 'Pyjoyment.Controller.object',
70
+ output: io.StringIO,
71
+ options: dict[str, Any],
72
+ ) -> None:
73
+ _render(renderer, c, output, options, Pyjo.Template.new(), c)
74
+
75
+
76
+ def _render(
77
+ renderer: 'Pyjoyment.Renderer.object',
78
+ c: 'Pyjoyment.Controller.object',
79
+ output: io.StringIO,
80
+ options: dict[str, Any],
81
+ mt: Pyjo.Template.object,
82
+ *args: Any,
83
+ ) -> None:
84
+ log = c.helpers.log()
85
+
86
+ # Cached
87
+ if mt.compiled is not None:
88
+ log.trace(f'Rendering cached {mt.name}')
89
+ result = mt.process(*args)
90
+
91
+ # Not cached
92
+ else:
93
+ inline = options.get('inline')
94
+ name = md5_sum(encode('UTF-8', inline)) if inline is not None else renderer.template_name(options)
95
+ if name is None:
96
+ return
97
+
98
+ # Inline
99
+ if inline is not None:
100
+ log.trace(f'Rendering inline template "{name}"')
101
+ mt.name = f'inline template "{name}"'
102
+ result = mt.render(inline, *args)
103
+
104
+ # File
105
+ else:
106
+ if encoding := renderer.encoding:
107
+ mt.encoding = encoding
108
+
109
+ # Try template
110
+ if (path := renderer.template_path(options)) is not None:
111
+ log.trace(f'Rendering template "{name}"')
112
+ mt.name = f'template "{name}"'
113
+ result = mt.render_file(path, *args)
114
+
115
+ # Try DATA section
116
+ elif (d := renderer.get_data_template(options)) is not None:
117
+ log.trace(f'Rendering template "{name}" from DATA section')
118
+ mt.name = f'template "{name}" from DATA section'
119
+ result = mt.render(d if isinstance(d, str) else d.decode('latin-1'), *args)
120
+
121
+ # No template
122
+ else:
123
+ log.trace(f'Template "{name}" not found')
124
+ return
125
+
126
+ # Exception
127
+ if isinstance(result, Pyjo.Exception.object):
128
+ raise result
129
+ output.write(result)
130
+
131
+
132
+ new = Pyjoyment_Plugin_EPLRenderer.new
133
+ object = Pyjoyment_Plugin_EPLRenderer
134
+ EPLRenderer = Pyjoyment_Plugin_EPLRenderer
@@ -0,0 +1,212 @@
1
+ """
2
+ Pyjoyment.Plugin.EPRenderer - Embedded Python renderer plugin
3
+ =============================================================
4
+ ::
5
+
6
+ # Pyjoyment
7
+ app.plugin('EPRenderer')
8
+ app.plugin('EPRenderer', name='foo')
9
+ app.plugin('EPRenderer', name='bar', template={'line_start': '.'})
10
+
11
+ # Pyjoyment.Lite
12
+ plugin('EPRenderer')
13
+ plugin('EPRenderer', name='foo')
14
+ plugin('EPRenderer', name='bar', template={'line_start': '.'})
15
+
16
+ :mod:`Pyjoyment.Plugin.EPRenderer` is a renderer for Embedded Python
17
+ templates, ``ep``, the default handler of :mod:`Pyjoyment`. They are
18
+ templates of :mod:`Pyjo.Template` with automatic escaping, in which the
19
+ values of the stash are variables, ``self`` and ``c`` are the current
20
+ controller, and the helpers are functions. ::
21
+
22
+ % title('Welcome!')
23
+ <p>Hello <%= name %>, this is <%= url_for('index') %>.</p>
24
+
25
+ A value of the stash hides a helper of the same name, which is still a
26
+ method of ``c``, as is the helper ``c``, which the variable ``c`` hides. ::
27
+
28
+ % c.title('Welcome!')
29
+ %= c.c('a', 'b', 'c').join(', ')
30
+
31
+ This is a core plugin, that means it is always enabled and its code a good
32
+ example for learning to build new plugins, you're welcome to fork it.
33
+
34
+ See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
35
+ default.
36
+
37
+ Options
38
+ -------
39
+
40
+ :mod:`Pyjoyment.Plugin.EPRenderer` supports the following options.
41
+
42
+ name ::
43
+
44
+ # Pyjoyment.Lite
45
+ plugin('EPRenderer', name='foo')
46
+
47
+ Handler name, defaults to ``ep``.
48
+
49
+ template ::
50
+
51
+ # Pyjoyment.Lite
52
+ plugin('EPRenderer', template={'line_start': '.'})
53
+
54
+ Attribute values passed to :mod:`Pyjo.Template` objects used to render
55
+ templates.
56
+
57
+ See also
58
+ --------
59
+
60
+ :mod:`Pyjoyment`, https://mojolicious.org.
61
+
62
+ Ported from `Mojolicious::Plugin::EPRenderer
63
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/EPRenderer.pm>`_ of Mojolicious 9.49.
64
+ """
65
+
66
+ import Pyjo.Base
67
+ import Pyjo.Exception
68
+ import Pyjo.Template
69
+ import Pyjoyment.Plugin.EPLRenderer
70
+
71
+ from Pyjo.Util import encode, md5_sum
72
+
73
+ import io
74
+ import re
75
+ import sys
76
+ import types
77
+ import weakref
78
+
79
+ from collections.abc import Callable
80
+ from typing import TYPE_CHECKING, Any, cast
81
+
82
+
83
+ if TYPE_CHECKING:
84
+ import Pyjoyment
85
+ import Pyjoyment.Controller
86
+ import Pyjoyment.Renderer
87
+
88
+
89
+ class Pyjoyment_Plugin_EPRenderer(Pyjoyment.Plugin.EPLRenderer.object):
90
+ """
91
+ :mod:`Pyjoyment.Plugin.EPRenderer` inherits all methods from
92
+ :mod:`Pyjoyment.Plugin.EPLRenderer` and implements the following new ones.
93
+ """
94
+
95
+ def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
96
+ """::
97
+
98
+ plugin.register(Pyjoyment.new())
99
+ plugin.register(Pyjoyment.new(), {'name': 'foo'})
100
+
101
+ Register renderer in :mod:`Pyjoyment` application.
102
+ """
103
+ conf = conf or {}
104
+ self._helpers = False
105
+
106
+ # Auto escape by default to prevent XSS attacks
107
+ ep: dict[str, Any] = {'auto_escape': True, **(conf.get('template') or {}), 'vars': True}
108
+ ns = ep['namespace'] = ep.get('namespace') or f'Pyjo.Template.Sandbox.{md5_sum(repr(self).encode())}'
109
+
110
+ # Unlike Mojolicious: the namespace is a module, which the plugin makes when it is not loaded, and the plugin
111
+ # removes the names that it adds, where Mojo removes all the names of the package, even of one that the option
112
+ # "template" names
113
+ module = sys.modules.get(ns)
114
+ if module is None:
115
+ module = sys.modules[ns] = types.ModuleType(ns)
116
+
117
+ # No spec, as the module has no source that linecache could read, which warns of a spec without a loader
118
+ del module.__spec__
119
+ else:
120
+ ns = None
121
+ names: list[str] = []
122
+ weakref.finalize(self, _teardown, module, names, ns)
123
+
124
+ # Make "self" and "c" available in templates
125
+ ep['prepend'] = 'self = c = _C()' + (f'\n{ep["prepend"]}' if ep.get('prepend') else '')
126
+
127
+ # Add "ep" handler and make it the default
128
+ def handler(
129
+ renderer: 'Pyjoyment.Renderer.object',
130
+ c: 'Pyjoyment.Controller.object',
131
+ output: io.StringIO,
132
+ options: dict[str, Any],
133
+ ) -> None:
134
+ name = options.get('inline')
135
+ if name is None:
136
+ name = renderer.template_name(options)
137
+ if name is None:
138
+ return
139
+ key = md5_sum(encode('UTF-8', name))
140
+
141
+ cache = renderer.cache
142
+ if (mt := cache.get(key)) is None:
143
+ cache.set(key, mt := Pyjo.Template.new(**ep))
144
+
145
+ # Export helpers only once
146
+ if not self._helpers:
147
+ self._helpers = True
148
+ _helpers(module, renderer.helpers, names)
149
+
150
+ # Make current controller available and render with "epl" handler
151
+ saved = module.__dict__.get('_C', _MISSING)
152
+ module.__dict__['_C'] = lambda: c
153
+ try:
154
+ Pyjoyment.Plugin.EPLRenderer._render( # pyright: ignore[reportPrivateUsage]
155
+ renderer, c, output, options, cast(Pyjo.Template.object, mt), c.stash()
156
+ )
157
+ except Pyjo.Exception.object as e:
158
+ # Not in Mojolicious: a value of the stash that hides a helper, which Perl tells apart by its sigil
159
+ if 'object is not callable' in e.message and (
160
+ hidden := [k for k, v in c.stash().items() if k in names and not callable(v)]
161
+ ):
162
+ e.message += ''.join(
163
+ f', the value "{k}" of the stash hides the helper {k}(), which c.{k}(...) calls' for k in hidden
164
+ )
165
+ raise
166
+ finally:
167
+ if saved is _MISSING:
168
+ module.__dict__.pop('_C', None)
169
+ else:
170
+ module.__dict__['_C'] = saved
171
+
172
+ app.renderer.default_handler = 'ep'
173
+ app.renderer.add_handler(conf.get('name') or 'ep', handler)
174
+
175
+
176
+ _MISSING: Any = Pyjo.Base.object()
177
+
178
+
179
+ # A helper of the templates, which the controller of the template calls, and whose missing attribute names the call of
180
+ # the helper, as ua.get_p(...) is a common mistake for ua().get_p(...)
181
+ class _Helper:
182
+ __slots__ = ('_module', '_name', '_sub')
183
+
184
+ def __init__(self, module: types.ModuleType, name: str, sub: Callable[..., Any]) -> None:
185
+ self._module, self._name, self._sub = module, name, sub
186
+
187
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
188
+ return self._sub(self._module.__dict__['_C'](), *args, **kwargs)
189
+
190
+ def __getattr__(self, name: str) -> Any:
191
+ if name.startswith('_'):
192
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'", name=name, obj=self)
193
+ message = f'Helper "{self._name}" has no attribute "{name}", call the helper first: {self._name}().{name}'
194
+ raise AttributeError(message, name=name, obj=self)
195
+
196
+
197
+ def _helpers(module: types.ModuleType, helpers: dict[str, Callable[..., Any]], names: list[str]) -> None:
198
+ for method in [method for method in helpers if re.fullmatch(r'\w+', method)]:
199
+ setattr(module, method, _Helper(module, method, helpers[method]))
200
+ names.append(method)
201
+
202
+
203
+ def _teardown(module: types.ModuleType, names: list[str], ns: str | None) -> None:
204
+ for name in [*names, '_C']:
205
+ module.__dict__.pop(name, None)
206
+ if ns is not None and sys.modules.get(ns) is module:
207
+ del sys.modules[ns]
208
+
209
+
210
+ new = Pyjoyment_Plugin_EPRenderer.new
211
+ object = Pyjoyment_Plugin_EPRenderer
212
+ EPRenderer = Pyjoyment_Plugin_EPRenderer
@@ -0,0 +1,98 @@
1
+ """
2
+ Pyjoyment.Plugin.HeaderCondition - Header condition plugin
3
+ ==========================================================
4
+ ::
5
+
6
+ # Pyjoyment
7
+ app.plugin('HeaderCondition')
8
+ app.get('/foo').requires(headers={'Referer': re.compile(r'example\\.com')})
9
+
10
+ # Pyjoyment.Lite
11
+ plugin('HeaderCondition')
12
+
13
+ @get('/').requires(headers={'Referer': re.compile(r'example\\.com')})
14
+ def index(c): ...
15
+
16
+ # All headers need to match
17
+ app.get('/foo').requires(headers={'X-Secret-Header': 'Foo', 'Referer': re.compile(r'example\\.com')})
18
+
19
+ # The "agent" condition is a shortcut for the "User-Agent" header
20
+ @get('/').requires(agent=re.compile(r'Firefox'))
21
+ def firefox(c): ...
22
+
23
+ # The "host" condition is a shortcut for the detected host
24
+ @get('/').requires(host=re.compile(r'mojolicious\\.org'))
25
+ def mojolicious(c): ...
26
+
27
+ :mod:`Pyjoyment.Plugin.HeaderCondition` is a route condition for header-based
28
+ routes.
29
+
30
+ This is a core plugin, that means it is always enabled and its code a good
31
+ example for learning to build new plugins, you're welcome to fork it.
32
+
33
+ See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
34
+ default.
35
+
36
+ See also
37
+ --------
38
+
39
+ :mod:`Pyjoyment`, https://mojolicious.org.
40
+
41
+ Ported from `Mojolicious::Plugin::HeaderCondition
42
+ <https://github.com/mojolicious/mojo/blob/v9.53/lib/Mojolicious/Plugin/HeaderCondition.pm>`_ of Mojolicious 9.53.
43
+ """
44
+
45
+ import Pyjoyment.Plugin
46
+
47
+ import re
48
+
49
+ from typing import TYPE_CHECKING, Any, cast
50
+
51
+
52
+ if TYPE_CHECKING:
53
+ import Pyjoyment
54
+ import Pyjoyment.Controller
55
+ import Pyjoyment.Routes.Route
56
+
57
+
58
+ class Pyjoyment_Plugin_HeaderCondition(Pyjoyment.Plugin.object):
59
+ """
60
+ :mod:`Pyjoyment.Plugin.HeaderCondition` inherits all methods from
61
+ :mod:`Pyjoyment.Plugin` and implements the following new ones.
62
+ """
63
+
64
+ def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
65
+ """::
66
+
67
+ plugin.register(Pyjoyment.new())
68
+
69
+ Register conditions in :mod:`Pyjoyment` application.
70
+ """
71
+ app.routes.add_condition('headers', _headers)
72
+ app.routes.add_condition('agent', lambda r, c, captures, agent: _headers(r, c, captures, {'User-Agent': agent}))
73
+ app.routes.add_condition('host', lambda r, c, captures, host: _check(c.req.url.to_abs().host, host))
74
+
75
+
76
+ def _check(value: str | None, pattern: Any) -> bool:
77
+ if value and isinstance(pattern, re.Pattern) and cast(re.Pattern[str], pattern).search(value):
78
+ return True
79
+ return bool(value) and pattern is not None and pattern == value
80
+
81
+
82
+ def _headers(
83
+ route: 'Pyjoyment.Routes.Route.object', c: 'Pyjoyment.Controller.object', captures: dict[str, Any], patterns: Any
84
+ ) -> bool:
85
+ if not patterns or not isinstance(patterns, dict):
86
+ return False
87
+
88
+ # All headers need to match
89
+ headers = c.req.headers
90
+ for name, pattern in cast(dict[str, Any], patterns).items():
91
+ if not _check(headers.header(name), pattern):
92
+ return False
93
+ return True
94
+
95
+
96
+ new = Pyjoyment_Plugin_HeaderCondition.new
97
+ object = Pyjoyment_Plugin_HeaderCondition
98
+ HeaderCondition = Pyjoyment_Plugin_HeaderCondition