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
Pyjo/Parameters.py ADDED
@@ -0,0 +1,472 @@
1
+ """
2
+ Pyjo.Parameters - Parameters
3
+ ============================
4
+ ::
5
+
6
+ from Pyjo.Parameters import Parameters
7
+
8
+ # Parse
9
+ params = Parameters('foo=bar&baz=23')
10
+ print(params.param('baz'))
11
+
12
+ # Or import the module, as in Google Python Style Guide
13
+
14
+ import Pyjo.Parameters
15
+
16
+ # Parse
17
+ params = Pyjo.Parameters.new('foo=bar&baz=23')
18
+ print(params.param('baz'))
19
+
20
+ # Build
21
+ params = Pyjo.Parameters.new('foo', 'bar', 'baz', 23)
22
+ params.pairs.append(('i', '♥ pyjoyment'))
23
+ print(params)
24
+
25
+ :mod:`Pyjo.Parameters` is a container for form parameters used by
26
+ :mod:`Pyjo.URL`, based on :rfc:`3986` and the
27
+ `HTML Living Standard <https://html.spec.whatwg.org>`_.
28
+
29
+ The parameters are pairs of a name and a value, as tuples. Methods that take
30
+ parameters take them as tuples, as names followed by their values, or as
31
+ keyword arguments: ::
32
+
33
+ params.append(('foo', 'bar'), ('baz', 23))
34
+ params.append('foo', 'bar', 'baz', 23)
35
+ params.append(foo='bar', baz=23)
36
+
37
+ See also
38
+ --------
39
+
40
+ :mod:`Pyjoyment`, https://mojolicious.org.
41
+
42
+ Ported from `Mojo::Parameters <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Parameters.pm>`_
43
+ of Mojolicious 9.49.
44
+
45
+ Classes
46
+ -------
47
+ """
48
+
49
+ import Pyjo.Base
50
+ import Pyjo.String.Mixin
51
+
52
+ from Pyjo.Util import decode, url_escape, url_unescape
53
+
54
+ import builtins
55
+ import re
56
+
57
+ from collections.abc import Iterator
58
+ from typing import Self, cast, overload
59
+
60
+
61
+ # The module ends with `object = Pyjo_Parameters`, which an annotation of
62
+ # `object` would mean, as annotations are evaluated lazily
63
+ type Anything = builtins.object
64
+
65
+
66
+ class Pyjo_Parameters(Pyjo.Base.object, Pyjo.String.Mixin.object):
67
+ """
68
+ :mod:`Pyjo.Parameters` inherits all methods from :mod:`Pyjo.Base` and
69
+ :mod:`Pyjo.String.Mixin` and implements the following new ones.
70
+ """
71
+
72
+ def __init__(self, *args: Anything, **kwargs: Anything) -> None:
73
+ """::
74
+
75
+ params = Pyjo.Parameters.new()
76
+ params = Pyjo.Parameters.new('foo=b%3Bar&baz=23')
77
+ params = Pyjo.Parameters.new('foo', 'b&ar')
78
+ params = Pyjo.Parameters.new('foo', ['ba&r', 'baz'])
79
+ params = Pyjo.Parameters.new('foo', ['bar', 'baz'], 'bar', 23)
80
+ params = Pyjo.Parameters.new(foo=['bar', 'baz'], bar=23)
81
+
82
+ Construct a new :mod:`Pyjo.Parameters` object and :meth:`parse` parameters
83
+ if necessary.
84
+ """
85
+ self.charset: str | None = 'UTF-8'
86
+ """::
87
+
88
+ charset = params.charset
89
+ params.charset = 'UTF-8'
90
+
91
+ Charset used for encoding and decoding parameters, defaults to ``UTF-8``. ::
92
+
93
+ # Disable encoding and decoding
94
+ params.charset = None
95
+ """
96
+
97
+ self._pairs: list[tuple[str, str]] | None = None
98
+ self._string: str | bytes | None = None
99
+
100
+ if args or kwargs:
101
+ self.parse(*args, **kwargs)
102
+
103
+ def __bool__(self) -> bool:
104
+ """::
105
+
106
+ boolean = bool(params)
107
+
108
+ Always true.
109
+ """
110
+ return True
111
+
112
+ def __getitem__(self, index: int) -> tuple[str, str]:
113
+ """::
114
+
115
+ pair = params[0]
116
+
117
+ Alias for :attr:`pairs`. Note that this will normalize the parameters. ::
118
+
119
+ print(params[0])
120
+ """
121
+ return self.pairs[index]
122
+
123
+ def __iter__(self) -> Iterator[tuple[str, str]]:
124
+ """::
125
+
126
+ pairs = list(params)
127
+
128
+ Alias for :attr:`pairs`. Note that this will normalize the parameters. ::
129
+
130
+ for name, value in params:
131
+ print(name, value)
132
+ """
133
+ return iter(self.pairs)
134
+
135
+ def __len__(self) -> int:
136
+ """::
137
+
138
+ size = len(params)
139
+
140
+ Alias for :attr:`pairs`. Note that this will normalize the parameters.
141
+ """
142
+ return len(self.pairs)
143
+
144
+ def __str__(self) -> str:
145
+ """::
146
+
147
+ string = str(params)
148
+
149
+ Alias for :meth:`to_str`.
150
+ """
151
+ return self.to_str()
152
+
153
+ def append(self, *args: Anything, **kwargs: Anything) -> Self:
154
+ """::
155
+
156
+ params = params.append('foo', 'ba&r')
157
+ params = params.append('foo', ['ba&r', 'baz'])
158
+ params = params.append('foo', ['bar', 'baz'], 'bar', 23)
159
+ params = params.append(Pyjo.Parameters.new())
160
+
161
+ Append parameters. Note that this method will normalize the parameters. ::
162
+
163
+ # "foo=bar&foo=baz"
164
+ Pyjo.Parameters.new('foo=bar').append(Pyjo.Parameters.new('foo=baz'))
165
+
166
+ # "foo=bar&foo=baz"
167
+ Pyjo.Parameters.new('foo=bar').append('foo', 'baz')
168
+
169
+ # "foo=bar&foo=baz&foo=yada"
170
+ Pyjo.Parameters.new('foo=bar').append('foo', ['baz', 'yada'])
171
+
172
+ # "foo=bar&foo=baz&foo=yada&bar=23"
173
+ Pyjo.Parameters.new('foo=bar').append('foo', ['baz', 'yada'], 'bar', 23)
174
+ """
175
+ old = self.pairs
176
+ for name, value in _pairs(args, kwargs):
177
+ # Multiple values
178
+ if isinstance(value, (list, tuple)):
179
+ old.extend((name, '' if v is None else _str(v)) for v in cast(list[Anything], value))
180
+
181
+ # Single value
182
+ elif value is not None:
183
+ old.append((name, _str(value)))
184
+
185
+ return self
186
+
187
+ def clone(self) -> Self:
188
+ """::
189
+
190
+ params2 = params.clone()
191
+
192
+ Return a new :mod:`Pyjo.Parameters` object cloned from these parameters.
193
+ """
194
+ clone = self.new()
195
+ clone.charset = self.charset
196
+ if self._string is not None:
197
+ clone._string = self._string
198
+ else:
199
+ clone._pairs = list(self.pairs)
200
+ return clone
201
+
202
+ def every_param(self, name: Anything) -> list[str]:
203
+ """::
204
+
205
+ values = params.every_param('foo')
206
+
207
+ Similar to :meth:`param`, but returns all values sharing the same name as a
208
+ list. Note that this method will normalize the parameters. ::
209
+
210
+ # Get first value
211
+ print(params.every_param('foo')[0])
212
+ """
213
+ name = str(name)
214
+ return [v for n, v in self.pairs if n == name]
215
+
216
+ def merge(self, *args: Anything, **kwargs: Anything) -> Self:
217
+ """::
218
+
219
+ params = params.merge('foo', 'ba&r')
220
+ params = params.merge('foo', ['ba&r', 'baz'])
221
+ params = params.merge('foo', ['bar', 'baz'], 'bar', 23)
222
+ params = params.merge(Pyjo.Parameters.new())
223
+
224
+ Merge parameters. Note that this method will normalize the parameters. ::
225
+
226
+ # "foo=baz"
227
+ Pyjo.Parameters.new('foo=bar').merge(Pyjo.Parameters.new('foo=baz'))
228
+
229
+ # "yada=yada&foo=baz"
230
+ Pyjo.Parameters.new('foo=bar&yada=yada').merge('foo', 'baz')
231
+
232
+ # "yada=yada"
233
+ Pyjo.Parameters.new('foo=bar&yada=yada').merge('foo', None)
234
+ """
235
+ if len(args) == 1 and not kwargs and isinstance(args[0], Pyjo_Parameters):
236
+ merge: dict[str, Anything] = {**args[0].to_dict()}
237
+ else:
238
+ merge = dict(_pairs(args, kwargs))
239
+
240
+ for name in sorted(merge):
241
+ if (value := merge[name]) is not None:
242
+ self.param(name, value)
243
+ else:
244
+ self.remove(name)
245
+
246
+ return self
247
+
248
+ def names(self) -> list[str]:
249
+ """::
250
+
251
+ names = params.names()
252
+
253
+ Return a list with all parameter names. ::
254
+
255
+ # Names of all parameters
256
+ for name in params.names():
257
+ print(name)
258
+ """
259
+ return sorted(self.to_dict())
260
+
261
+ @property
262
+ def pairs(self) -> list[tuple[str, str]]:
263
+ """::
264
+
265
+ pairs = params.pairs
266
+ params.pairs = [('foo', 'b&ar'), ('baz', '23')]
267
+
268
+ Parsed parameter pairs. Note that this method will normalize the parameters. ::
269
+
270
+ # Remove all parameters
271
+ params.pairs = []
272
+ """
273
+ # Parse string
274
+ if (string := self._string) is not None:
275
+ self._string = None
276
+ pairs: list[tuple[str, str]] = []
277
+ self._pairs = pairs
278
+
279
+ # Not in Mojolicious: bytes, which are a Perl byte string, the characters 0-255
280
+ if isinstance(string, (bytes, bytearray)):
281
+ string = string.decode('latin-1')
282
+
283
+ for pair in string.split('&'):
284
+ if not (m := re.match(r'^([^=]+)(?:=(.*))?$', pair)):
285
+ continue
286
+ name, value = m[1], m[2] or ''
287
+
288
+ # Replace "+" with whitespace, unescape and decode
289
+ name = self._decode(url_unescape(name.replace('+', ' ')))
290
+ value = self._decode(url_unescape(value.replace('+', ' ')))
291
+
292
+ pairs.append((name, value))
293
+
294
+ if self._pairs is None:
295
+ self._pairs = []
296
+ return self._pairs
297
+
298
+ @pairs.setter
299
+ def pairs(self, value: list[tuple[str, str]]) -> None:
300
+ self._pairs = value
301
+ self._string = None
302
+
303
+ @overload
304
+ def param(self, name: Anything, /) -> str | None: ...
305
+
306
+ @overload
307
+ def param(self, name: Anything, value: Anything, /, *values: Anything) -> Self: ...
308
+
309
+ def param(self, name: Anything, /, *values: Anything) -> str | None | Self:
310
+ """::
311
+
312
+ value = params.param('foo')
313
+ params = params.param('foo', 'ba&r')
314
+ params = params.param('foo', 'ba&r', 'baz')
315
+ params = params.param('foo', ['ba;r', 'baz'])
316
+
317
+ Access parameter values. If there are multiple values sharing the same name,
318
+ and you want to access more than just the last one, you can use
319
+ :meth:`every_param`. Note that this method will normalize the parameters.
320
+ """
321
+ if not values:
322
+ every = self.every_param(name)
323
+ return every[-1] if every else None
324
+ self.remove(name)
325
+ value = cast(list[Anything], values[0]) if len(values) == 1 and isinstance(values[0], list) else list(values)
326
+ return self.append(name, value)
327
+
328
+ def parse(self, *args: Anything, **kwargs: Anything) -> Self:
329
+ """::
330
+
331
+ params = params.parse('foo=b%3Bar&baz=23')
332
+ params = params.parse(parse_qsl('foo=b%3Bar&baz=23'))
333
+
334
+ Parse parameters, given as characters or as bytes encoded with
335
+ :attr:`charset`. A list of pairs, as :func:`urllib.parse.parse_qsl` gives, or
336
+ a ``dict``, as :func:`urllib.parse.parse_qs` gives, is appended.
337
+ """
338
+ # Pairs
339
+ # Not in Mojolicious: a list of pairs or a dict, as urllib.parse.parse_qsl
340
+ # and parse_qs give
341
+ if len(args) > 1 or kwargs or (args and isinstance(args[0], (tuple, list, dict, Pyjo_Parameters))):
342
+ return self.append(*args, **kwargs)
343
+
344
+ # String
345
+ # Not in Mojolicious: bytes, encoded with the charset
346
+ string = args[0] if args else None
347
+ self._string = (
348
+ '' if string is None else bytes(string) if isinstance(string, (bytes, bytearray)) else str(string)
349
+ )
350
+ self._pairs = None
351
+ return self
352
+
353
+ def remove(self, name: Anything) -> Self:
354
+ """::
355
+
356
+ params = params.remove('foo')
357
+
358
+ Remove parameters. Note that this method will normalize the parameters. ::
359
+
360
+ # "bar=yada"
361
+ Pyjo.Parameters.new('foo=bar&foo=baz&bar=yada').remove('foo')
362
+ """
363
+ name = str(name)
364
+ pairs = self.pairs
365
+ pairs[:] = [(n, v) for n, v in pairs if n != name]
366
+ return self
367
+
368
+ def to_dict(self) -> dict[str, str | list[str]]:
369
+ """::
370
+
371
+ d = params.to_dict()
372
+
373
+ Turn parameters into a dict. Note that this method will normalize the
374
+ parameters. ::
375
+
376
+ # "baz"
377
+ Pyjo.Parameters.new('foo=bar&foo=baz').to_dict()['foo'][1]
378
+ """
379
+ d: dict[str, str | list[str]] = {}
380
+ for name, value in self.pairs:
381
+ # List
382
+ if (old := d.get(name)) is not None:
383
+ if isinstance(old, list):
384
+ old.append(value)
385
+ else:
386
+ d[name] = [old, value]
387
+
388
+ # String
389
+ else:
390
+ d[name] = value
391
+
392
+ return d
393
+
394
+ def to_str(self) -> str:
395
+ """::
396
+
397
+ string = params.to_str()
398
+
399
+ Turn parameters into a string. ::
400
+
401
+ # "foo=bar&baz=23"
402
+ Pyjo.Parameters.new().set(pairs=[('foo', 'bar'), ('baz', '23')]).to_str()
403
+ """
404
+ # String (RFC 3986)
405
+ if (string := self._string) is not None:
406
+ return url_escape(self._encode(string), rb"^A-Za-z0-9\-._~%!$&'()*+,;=:@/?").decode('ascii')
407
+
408
+ # Build pairs (HTML Living Standard)
409
+ pairs: list[str] = []
410
+ for name, value in self.pairs:
411
+ # Escape and replace whitespace with "+"
412
+ name = url_escape(self._encode(name), rb'^*\-.0-9A-Z_a-z').decode('ascii').replace('%20', '+')
413
+ value = url_escape(self._encode(value), rb'^*\-.0-9A-Z_a-z').decode('ascii').replace('%20', '+')
414
+ pairs.append(f'{name}={value}')
415
+
416
+ return '&'.join(pairs)
417
+
418
+ # Not in Mojolicious: urllib of the standard library
419
+ def to_urllib(self) -> list[tuple[str, str]]:
420
+ """::
421
+
422
+ pairs = params.to_urllib()
423
+
424
+ Turn parameters into a list of pairs, as :func:`urllib.parse.parse_qsl` gives,
425
+ for the standard library. ::
426
+
427
+ # "foo=bar&foo=baz"
428
+ urlencode(Pyjo.Parameters.new('foo=bar&foo=baz').to_urllib())
429
+ """
430
+ return list(self.pairs)
431
+
432
+ def _decode(self, string: str) -> str:
433
+ # What does not decode stays characters 0-255, as a Perl byte string
434
+ decoded = decode(self.charset or 'latin-1', string)
435
+ return decoded if decoded is not None else string
436
+
437
+ def _encode(self, string: str | bytes) -> bytes:
438
+ # Without a charset, a character is a byte, as in a Perl byte string
439
+ return string if isinstance(string, bytes) else string.encode(self.charset or 'latin-1')
440
+
441
+
442
+ # A value as a string, and bytes as the characters 0-255, as Perl has a byte string, which a charset encodes as
443
+ # characters, as Mojo does
444
+ def _str(value: Anything) -> str:
445
+ return value.decode('latin-1') if isinstance(value, (bytes, bytearray)) else str(value)
446
+
447
+
448
+ def _pairs(args: tuple[Anything, ...], kwargs: dict[str, Anything]) -> Iterator[tuple[str, Anything]]:
449
+ # Parameters as pairs: from another object, as tuples, as names followed by
450
+ # their values, and as keyword arguments
451
+ # Not in Mojolicious: tuples and keyword arguments, next to Mojo's names
452
+ # followed by their values
453
+ items = iter(args)
454
+ for item in items:
455
+ if isinstance(item, Pyjo_Parameters):
456
+ yield from item.pairs
457
+ elif isinstance(item, tuple):
458
+ name, value = cast(tuple[Anything, Anything], item)
459
+ yield str(name), value
460
+ # Not in Mojolicious: a list of pairs or a dict of the standard library
461
+ elif isinstance(item, list):
462
+ yield from _pairs(tuple(cast(list[Anything], item)), {})
463
+ elif isinstance(item, dict):
464
+ yield from ((str(n), v) for n, v in cast(dict[Anything, Anything], item).items())
465
+ else:
466
+ yield str(item), next(items, None)
467
+ yield from kwargs.items()
468
+
469
+
470
+ new = Pyjo_Parameters.new
471
+ object = Pyjo_Parameters
472
+ Parameters = Pyjo_Parameters