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,429 @@
1
+ r"""
2
+ Pyjo.Transaction - Transaction base class
3
+ =========================================
4
+ ::
5
+
6
+ from Pyjo.Transaction import Transaction
7
+
8
+ class MyTransaction(Transaction):
9
+ def client_read(self, chunk): ...
10
+ def client_write(self): ...
11
+ def server_read(self, chunk): ...
12
+ def server_write(self): ...
13
+
14
+ # Or import the module, as in Google Python Style Guide
15
+
16
+ import Pyjo.Transaction
17
+
18
+ class MyTransaction(Pyjo.Transaction.object):
19
+ def client_read(self, chunk): ...
20
+ def client_write(self): ...
21
+ def server_read(self, chunk): ...
22
+ def server_write(self): ...
23
+
24
+ :mod:`Pyjo.Transaction` is an abstract base class for transactions, like
25
+ :mod:`Pyjo.Transaction.HTTP` and :mod:`Pyjo.Transaction.WebSocket`.
26
+
27
+ Events
28
+ ------
29
+
30
+ :mod:`Pyjo.Transaction` inherits all events from :mod:`Pyjo.EventEmitter` and
31
+ can emit the following new ones.
32
+
33
+ connection
34
+ ~~~~~~~~~~
35
+ ::
36
+
37
+ tx.on('connection', lambda tx, connection: ...)
38
+
39
+ Emitted when a connection has been assigned to transaction.
40
+
41
+ finish
42
+ ~~~~~~
43
+ ::
44
+
45
+ tx.on('finish', lambda tx: ...)
46
+
47
+ Emitted when transaction is finished.
48
+
49
+ See also
50
+ --------
51
+
52
+ :mod:`Pyjoyment`, https://mojolicious.org.
53
+
54
+ Ported from `Mojo::Transaction <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Transaction.pm>`_
55
+ of Mojolicious 9.49.
56
+ """
57
+
58
+ import Pyjo.EventEmitter
59
+ import Pyjo.Exception
60
+ import Pyjo.Message
61
+ import Pyjo.Message.Request
62
+ import Pyjo.Message.Response
63
+
64
+ from Pyjo.String.Unicode import split
65
+ from Pyjo.Util import network_contains
66
+
67
+ from typing import TYPE_CHECKING, Any, Self
68
+
69
+
70
+ class Pyjo_Transaction(Pyjo.EventEmitter.object):
71
+ """
72
+ :mod:`Pyjo.Transaction` inherits all methods from :mod:`Pyjo.EventEmitter`
73
+ and implements the following new ones.
74
+ """
75
+
76
+ def __init__(
77
+ self,
78
+ *,
79
+ kept_alive: bool | None = None,
80
+ local_address: str | None = None,
81
+ local_port: int | None = None,
82
+ original_remote_address: str | None = None,
83
+ remote_port: int | None = None,
84
+ req: Pyjo.Message.Request.object | None = None,
85
+ res: Pyjo.Message.Response.object | None = None,
86
+ ) -> None:
87
+ """::
88
+
89
+ tx = Pyjo.Transaction.HTTP.new()
90
+ tx = Pyjo.Transaction.HTTP.new(req=Pyjo.Message.Request.new())
91
+
92
+ Construct a new transaction object.
93
+ """
94
+ super().__init__()
95
+
96
+ self.original_remote_address = original_remote_address
97
+ """::
98
+
99
+ address = tx.original_remote_address
100
+ tx.original_remote_address = '127.0.0.1'
101
+
102
+ Remote interface address.
103
+ """
104
+
105
+ self._kept_alive = kept_alive
106
+ self._local_address = local_address
107
+ self._local_port = local_port
108
+ self._remote_port = remote_port
109
+ self._req = req
110
+ self._res = res
111
+
112
+ # The state of the transaction, as the keys of Mojo's hash
113
+ self._completed = False
114
+ self._connection: str | None = None
115
+
116
+ if not TYPE_CHECKING:
117
+ # Not in Mojolicious: the transaction of a blocking request used as a decorator, as @ua.get(url) over a
118
+ # callback; out of the sight of type checkers, which would take a transaction for a callable
119
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
120
+ raise TypeError(
121
+ f"'{type(self).__name__}' object is not callable: a request without a callback is blocking and returns"
122
+ ' its transaction, a non-blocking one takes its callback as the last argument or cb=..., or is a promise'
123
+ ' of get_p(...)'
124
+ )
125
+
126
+ @property
127
+ def kept_alive(self) -> bool | None:
128
+ """::
129
+
130
+ boolean = tx.kept_alive
131
+ tx.kept_alive = boolean
132
+
133
+ Connection has been kept alive.
134
+ """
135
+ return self._kept_alive
136
+
137
+ @kept_alive.setter
138
+ def kept_alive(self, value: bool | None) -> None:
139
+ self._kept_alive = value
140
+
141
+ @property
142
+ def local_address(self) -> str | None:
143
+ """::
144
+
145
+ address = tx.local_address
146
+ tx.local_address = '127.0.0.1'
147
+
148
+ Local interface address.
149
+ """
150
+ return self._local_address
151
+
152
+ @local_address.setter
153
+ def local_address(self, value: str | None) -> None:
154
+ self._local_address = value
155
+
156
+ @property
157
+ def local_port(self) -> int | None:
158
+ """::
159
+
160
+ port = tx.local_port
161
+ tx.local_port = 8080
162
+
163
+ Local interface port.
164
+ """
165
+ return self._local_port
166
+
167
+ @local_port.setter
168
+ def local_port(self, value: int | None) -> None:
169
+ self._local_port = value
170
+
171
+ @property
172
+ def remote_port(self) -> int | None:
173
+ """::
174
+
175
+ port = tx.remote_port
176
+ tx.remote_port = 8081
177
+
178
+ Remote interface port.
179
+ """
180
+ return self._remote_port
181
+
182
+ @remote_port.setter
183
+ def remote_port(self, value: int | None) -> None:
184
+ self._remote_port = value
185
+
186
+ @property
187
+ def req(self) -> Pyjo.Message.Request.object:
188
+ """::
189
+
190
+ req = tx.req
191
+ tx.req = Pyjo.Message.Request.new()
192
+
193
+ HTTP request, defaults to a :mod:`Pyjo.Message.Request` object. ::
194
+
195
+ # Access request information
196
+ method = tx.req.method
197
+ url = tx.req.url.to_abs()
198
+ info = tx.req.url.to_abs().userinfo
199
+ host = tx.req.url.to_abs().host
200
+ agent = tx.req.headers.user_agent
201
+ custom = tx.req.headers.header('Custom-Header')
202
+ data = tx.req.body
203
+ string = tx.req.text
204
+ params = tx.req.params.to_dict()
205
+ all = tx.req.uploads()
206
+ value = tx.req.json()
207
+ foo = tx.req.json('/23/foo')
208
+ dom = tx.req.dom()
209
+ bar = tx.req.dom('div.bar').first().text
210
+ """
211
+ if self._req is None:
212
+ self._req = Pyjo.Message.Request.new()
213
+ return self._req
214
+
215
+ @req.setter
216
+ def req(self, value: Pyjo.Message.Request.object) -> None:
217
+ self._req = value
218
+
219
+ @property
220
+ def res(self) -> Pyjo.Message.Response.object:
221
+ """::
222
+
223
+ res = tx.res
224
+ tx.res = Pyjo.Message.Response.new()
225
+
226
+ HTTP response, defaults to a :mod:`Pyjo.Message.Response` object. ::
227
+
228
+ # Access response information
229
+ code = tx.res.code
230
+ message = tx.res.message
231
+ server = tx.res.headers.server
232
+ custom = tx.res.headers.header('Custom-Header')
233
+ data = tx.res.body
234
+ string = tx.res.text
235
+ value = tx.res.json()
236
+ foo = tx.res.json('/23/foo')
237
+ dom = tx.res.dom()
238
+ bar = tx.res.dom('div.bar').first().text
239
+ """
240
+ if self._res is None:
241
+ self._res = Pyjo.Message.Response.new()
242
+ return self._res
243
+
244
+ @res.setter
245
+ def res(self, value: Pyjo.Message.Response.object) -> None:
246
+ self._res = value
247
+
248
+ def client_read(self, chunk: bytes) -> object:
249
+ """::
250
+
251
+ tx.client_read(data)
252
+
253
+ Read data client-side, used to implement user agents such as
254
+ :mod:`Pyjo.UserAgent`. Meant to be overloaded in a subclass.
255
+ """
256
+ raise NotImplementedError('Method "client_read" not implemented by subclass')
257
+
258
+ def client_write(self) -> bytes:
259
+ """::
260
+
261
+ data = tx.client_write()
262
+
263
+ Write data client-side, used to implement user agents such as
264
+ :mod:`Pyjo.UserAgent`. Meant to be overloaded in a subclass.
265
+ """
266
+ raise NotImplementedError('Method "client_write" not implemented by subclass')
267
+
268
+ def closed(self) -> Self:
269
+ """::
270
+
271
+ tx = tx.closed()
272
+
273
+ Same as :meth:`completed`, but also indicates that all transaction data
274
+ has been sent.
275
+ """
276
+ return self.completed().emit('finish')
277
+
278
+ def completed(self) -> Self:
279
+ """::
280
+
281
+ tx = tx.completed()
282
+
283
+ Low-level method to finalize transaction.
284
+ """
285
+ self._completed = True
286
+ return self
287
+
288
+ @property
289
+ def connection(self) -> str | None:
290
+ """::
291
+
292
+ id = tx.connection
293
+ tx.connection = id
294
+
295
+ Connection identifier, setting it emits the ``connection`` event.
296
+ """
297
+ return self._connection
298
+
299
+ @connection.setter
300
+ def connection(self, value: str | None) -> None:
301
+ self._connection = value
302
+ self.emit('connection', value)
303
+
304
+ @property
305
+ def error(self) -> Pyjo.Message.Error | None:
306
+ """::
307
+
308
+ err = tx.error
309
+
310
+ Get request or response error and return ``None`` if there is no
311
+ error. ::
312
+
313
+ # Longer version
314
+ err = tx.req.error or tx.res.error
315
+
316
+ # Check for 4xx/5xx response and connection errors
317
+ if err := tx.error:
318
+ if 'code' in err:
319
+ raise Exception(f'{err["code"]} response: {err["message"]}')
320
+ raise Exception(f'Connection error: {err["message"]}')
321
+ """
322
+ return self.req.error or self.res.error
323
+
324
+ @property
325
+ def is_finished(self) -> bool:
326
+ """::
327
+
328
+ boolean = tx.is_finished
329
+
330
+ Check if transaction is finished.
331
+ """
332
+ return self._completed
333
+
334
+ @property
335
+ def is_websocket(self) -> bool:
336
+ """::
337
+
338
+ boolean = tx.is_websocket
339
+
340
+ False, this is not a :mod:`Pyjo.Transaction.WebSocket` object.
341
+ """
342
+ return False
343
+
344
+ @property
345
+ def remote_address(self) -> str | None:
346
+ """::
347
+
348
+ address = tx.remote_address
349
+ tx.remote_address = '127.0.0.1'
350
+
351
+ Same as :attr:`original_remote_address` unless :attr:`req` has been
352
+ performed via a
353
+ :attr:`Pyjo.Message.Request.Pyjo_Message_Request.reverse_proxy`. If so
354
+ then the last value of ``X-Forwarded-For`` header is returned.
355
+ Additionally if
356
+ :attr:`Pyjo.Message.Request.Pyjo_Message_Request.trusted_proxies` are
357
+ also provided then the original address must be trusted and any
358
+ ``X-Forwarded-For`` entries that are trusted are ignored, returning
359
+ the last untrusted address or the first address if all are trusted.
360
+ """
361
+ if not self.req.reverse_proxy:
362
+ return self.original_remote_address
363
+
364
+ # Reverse proxy
365
+ forwarded = split(r'\s*,\s*', self.req.headers.header('X-Forwarded-For') or '')
366
+ addrs: list[str | None] = [a for a in forwarded if a is not None]
367
+ trusted = self.req.trusted_proxies
368
+ if not len(trusted):
369
+ return addrs[-1] if addrs else self.original_remote_address
370
+
371
+ addrs.append(self.original_remote_address)
372
+ for addr in reversed(addrs):
373
+ if not any(network_contains(t, addr or '') for t in trusted):
374
+ return addr
375
+ return addrs[0]
376
+
377
+ @remote_address.setter
378
+ def remote_address(self, value: str | None) -> None:
379
+ self.original_remote_address = value
380
+
381
+ @property
382
+ def result(self) -> Pyjo.Message.Response.object:
383
+ """::
384
+
385
+ res = tx.result
386
+
387
+ Returns the :mod:`Pyjo.Message.Response` object from :attr:`res` or
388
+ raises an exception if a connection error has occurred. ::
389
+
390
+ # Fine grained response handling (raises on connection errors)
391
+ res = tx.result
392
+ if res.is_success:
393
+ print(res.body)
394
+ elif res.is_error:
395
+ print(res.message)
396
+ elif res.code == 301:
397
+ print(res.headers.location)
398
+ else:
399
+ print('Whatever...')
400
+ """
401
+ err = self.error
402
+ if not err or err.get('code'):
403
+ return self.res
404
+ raise Pyjo.Exception.new(err['message'])
405
+
406
+ def server_read(self, chunk: bytes) -> object:
407
+ """::
408
+
409
+ tx.server_read(data)
410
+
411
+ Read data server-side, used to implement web servers such as
412
+ :mod:`Pyjo.Server.Daemon`. Meant to be overloaded in a subclass.
413
+ """
414
+ raise NotImplementedError('Method "server_read" not implemented by subclass')
415
+
416
+ def server_write(self) -> bytes:
417
+ """::
418
+
419
+ data = tx.server_write()
420
+
421
+ Write data server-side, used to implement web servers such as
422
+ :mod:`Pyjo.Server.Daemon`. Meant to be overloaded in a subclass.
423
+ """
424
+ raise NotImplementedError('Method "server_write" not implemented by subclass')
425
+
426
+
427
+ new = Pyjo_Transaction.new
428
+ object = Pyjo_Transaction
429
+ Transaction = Pyjo_Transaction