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,128 @@
1
+ """
2
+ Pyjoyment.Command.eval - Eval command
3
+ =====================================
4
+ ::
5
+
6
+ Usage: APPLICATION eval [OPTIONS] CODE
7
+
8
+ ./myapp.py eval 'print(app.ua.get("/").result.body)'
9
+ ./myapp.py eval 'print(*sorted(app.renderer.helpers), sep="\\n")'
10
+ ./myapp.py eval -v 'app.home'
11
+ ./myapp.py eval -V 'app.renderer.paths'
12
+
13
+ Options:
14
+ -h, --help Show this summary of available options
15
+ --home <path> Path to home directory of your application,
16
+ defaults to the value of PYJO_HOME or
17
+ auto-detection
18
+ --log-format <name> Format of log messages, "text" or "json",
19
+ defaults to the value of PYJO_LOG_FORMAT or
20
+ "text"
21
+ -m, --mode <name> Operating mode for your application, defaults to
22
+ the value of PYJO_MODE or "development"
23
+ -v, --verbose Print return value to sys.stdout
24
+ -V Print returned data structure to sys.stdout
25
+
26
+ :mod:`Pyjoyment.Command.eval` runs code against applications. The code
27
+ is Python, with the application as ``app``, and its result is the value of its
28
+ last expression. If the result is a promise (then-able), it will wait until
29
+ the promise is fulfilled or rejected and the result is returned.
30
+
31
+ This is a core command, that means it is always enabled and its code a good
32
+ example for learning to build new commands, you're welcome to fork it.
33
+
34
+ See :mod:`Pyjoyment.Commands` for a list of commands that are available by
35
+ default.
36
+
37
+ See also
38
+ --------
39
+
40
+ :mod:`Pyjoyment`, https://mojolicious.org.
41
+
42
+ Ported from `Mojolicious::Command::eval
43
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Command/eval.pm>`_ of Mojolicious 9.49.
44
+
45
+ Classes
46
+ -------
47
+ """
48
+
49
+ import Pyjo.Exception
50
+ import Pyjo.Promise
51
+ import Pyjoyment.Command
52
+
53
+ from Pyjo.Util import extract_usage, getopt
54
+
55
+ import ast
56
+
57
+ from typing import Any
58
+
59
+
60
+ class Pyjoyment_Command_eval(Pyjoyment.Command.object):
61
+ """
62
+ :mod:`Pyjoyment.Command.eval` inherits all attributes and methods from
63
+ :mod:`Pyjoyment.Command` and implements the following new ones.
64
+ """
65
+
66
+ description = 'Run code against application'
67
+ """::
68
+
69
+ description = eval.description
70
+ eval.description = 'Foo'
71
+
72
+ Short description of this command, used for the command list.
73
+ """
74
+
75
+ usage = extract_usage()
76
+ """::
77
+
78
+ usage = eval.usage
79
+ eval.usage = 'Foo'
80
+
81
+ Usage information for this command, used for the help screen.
82
+ """
83
+
84
+ def run(self, *args: str) -> Any:
85
+ """::
86
+
87
+ eval.run(*sys.argv[1:])
88
+
89
+ Run this command.
90
+ """
91
+ arguments = list(args)
92
+ if not (opts := getopt(arguments, {'v|verbose': None, 'V': None})):
93
+ raise Pyjo.Exception.new(self.usage)
94
+ code = arguments.pop(0) if arguments else ''
95
+
96
+ # Run code against application
97
+ app = self.app
98
+ # Unlike Mojolicious: Python code, whose value is the one of its last expression, as the value of Perl code is
99
+ # the one of its last statement
100
+ result = _eval(code, {'__name__': '__main__', 'app': app})
101
+
102
+ # Handle promises
103
+ values: list[Any] = []
104
+ errors: list[Any] = []
105
+ Pyjo.Promise.resolve(result).then(values.append, errors.append).wait()
106
+ if errors:
107
+ raise errors[0] if isinstance(errors[0], BaseException) else Pyjo.Exception.new(str(errors[0]))
108
+ result = values[0] if values else None
109
+ if result is None or not (opts.verbose or opts.V):
110
+ return result
111
+ if opts.V:
112
+ print(app.dumper(result), end='')
113
+ else:
114
+ print(result)
115
+ return None
116
+
117
+
118
+ def _eval(code: str, namespace: dict[str, Any]) -> Any:
119
+ tree = ast.parse(code, '-e')
120
+ last = tree.body.pop() if tree.body and isinstance(tree.body[-1], ast.Expr) else None
121
+ exec(compile(tree, '-e', 'exec'), namespace)
122
+ if not isinstance(last, ast.Expr):
123
+ return None
124
+ return eval(compile(ast.Expression(last.value), '-e', 'eval'), namespace)
125
+
126
+
127
+ new = Pyjoyment_Command_eval.new
128
+ object = Pyjoyment_Command_eval
@@ -0,0 +1,99 @@
1
+ """
2
+ Pyjoyment.Command.fastcgi - FastCGI command
3
+ ===========================================
4
+ ::
5
+
6
+ Usage: APPLICATION fastcgi [OPTIONS]
7
+
8
+ ./myapp.py fastcgi
9
+
10
+ Options:
11
+ -h, --help Show this summary of available options
12
+ --home <path> Path to home directory of your application,
13
+ defaults to the value of PYJO_HOME or
14
+ auto-detection
15
+ --log-format <name> Format of log messages, "text" or "json",
16
+ defaults to the value of PYJO_LOG_FORMAT or
17
+ "text"
18
+ -m, --mode <name> Operating mode for your application, defaults to
19
+ the value of PYJO_MODE or "development"
20
+
21
+ :mod:`Pyjoyment.Command.fastcgi` starts applications that a web server starts
22
+ as FastCGI applications, as Apache with ``mod_fcgid`` and lighttpd do: it
23
+ serves the listen socket that the web server gives the application as the
24
+ file descriptor ``0``, with :mod:`Pyjo.Server.Daemon` and
25
+ :mod:`Pyjo.Server.FastCGI`, and is detected by :mod:`Pyjoyment.Commands`
26
+ when the file descriptor ``0`` is such a socket. A FastCGI application that
27
+ listens on a socket of its own, which the web server connects to, is started
28
+ with the commands ``daemon`` and ``prefork`` and a listen location
29
+ ``fcgi://`` or ``fcgi+unix://``.
30
+
31
+ This is a core command, that means it is always enabled and its code a good
32
+ example for learning to build new commands, you're welcome to fork it.
33
+
34
+ See :mod:`Pyjoyment.Commands` for a list of commands that are available by
35
+ default.
36
+
37
+ See also
38
+ --------
39
+
40
+ :mod:`Pyjoyment`, https://mojolicious.org.
41
+
42
+ It has no counterpart in Mojolicious; :mod:`Pyjoyment.Command.cgi` is the
43
+ counterpart of `Mojolicious::Command::cgi
44
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Command/cgi.pm>`_.
45
+
46
+ Classes
47
+ -------
48
+ """
49
+
50
+ import Pyjo.Exception
51
+ import Pyjo.Server.Daemon
52
+ import Pyjoyment.Command
53
+
54
+ from Pyjo.Util import extract_usage, getopt
55
+
56
+ from typing import Any
57
+
58
+
59
+ class Pyjoyment_Command_fastcgi(Pyjoyment.Command.object):
60
+ """
61
+ :mod:`Pyjoyment.Command.fastcgi` inherits all attributes and methods from
62
+ :mod:`Pyjoyment.Command` and implements the following new ones.
63
+ """
64
+
65
+ description = 'Start application with FastCGI of the web server'
66
+ """::
67
+
68
+ description = fastcgi.description
69
+ fastcgi.description = 'Foo'
70
+
71
+ Short description of this command, used for the command list.
72
+ """
73
+
74
+ usage = extract_usage()
75
+ """::
76
+
77
+ usage = fastcgi.usage
78
+ fastcgi.usage = 'Foo'
79
+
80
+ Usage information for this command, used for the help screen.
81
+ """
82
+
83
+ def run(self, *args: str) -> Any:
84
+ """::
85
+
86
+ fastcgi.run(*sys.argv[1:])
87
+
88
+ Run this command.
89
+ """
90
+ arguments = list(args)
91
+ if not getopt(arguments, {}) or len(arguments):
92
+ raise Pyjo.Exception.new(self.usage)
93
+
94
+ # The listen socket of the web server, silent, as the web server does not read the output of the application
95
+ Pyjo.Server.Daemon.new(app=self.app, listen=['fcgi://*?fd=0'], silent=True).run()
96
+
97
+
98
+ new = Pyjoyment_Command_fastcgi.new
99
+ object = Pyjoyment_Command_fastcgi
@@ -0,0 +1,305 @@
1
+ """
2
+ Pyjoyment.Command.get - Get command
3
+ ===================================
4
+ ::
5
+
6
+ Usage: APPLICATION get [OPTIONS] URL [SELECTOR|JSON-POINTER] [COMMANDS]
7
+
8
+ ./myapp.py get /
9
+ ./myapp.py get -H 'Accept: text/html' /hello.html 'head > title' text
10
+ ./myapp.py get //sri:secr3t@/secrets.json /1/content
11
+ pyjo get mojolicious.org
12
+ pyjo get -v -r -o 25 -i 50 google.com
13
+ pyjo get -v -H 'Host: mojolicious.org' -H 'Accept: */*' mojolicious.org
14
+ pyjo get -u 'sri:s3cret' https://mojolicious.org
15
+ pyjo get mojolicious.org > example.html
16
+ pyjo get -M PUT mojolicious.org < example.html
17
+ pyjo get -f 'q=Mojolicious' -f 'size=5' https://metacpan.org/search
18
+ pyjo get -M POST -f 'upload=@example.html' mojolicious.org
19
+ pyjo get mojolicious.org 'head > title' text
20
+ pyjo get mojolicious.org .footer all
21
+ pyjo get mojolicious.org a attr href
22
+ pyjo get mojolicious.org '*' attr id
23
+ pyjo get mojolicious.org 'h1, h2, h3' 3 text
24
+ pyjo get https://pypi.org/pypi/Pyjoyment/json /info/version
25
+ pyjo get -H 'Host: example.com' http+unix://%2Ftmp%2Fmyapp.sock/index.html
26
+
27
+ Options:
28
+ -C, --charset <charset> Charset of HTML/XML content, defaults
29
+ to auto-detection
30
+ -c, --content <content> Content to send with request
31
+ -f, --form <name=value> One or more form values and file
32
+ uploads
33
+ -H, --header <name:value> One or more additional HTTP headers
34
+ -h, --help Show this summary of available options
35
+ --home <path> Path to home directory of your
36
+ application, defaults to the value of
37
+ PYJO_HOME or auto-detection
38
+ -i, --inactivity-timeout <seconds> Inactivity timeout, defaults to the
39
+ value of PYJO_INACTIVITY_TIMEOUT or 40
40
+ -k, --insecure Do not require a valid TLS certificate
41
+ to access HTTPS sites
42
+ --log-format <name> Format of log messages, "text" or
43
+ "json", defaults to the value of
44
+ PYJO_LOG_FORMAT or "text"
45
+ -M, --method <method> HTTP method to use, defaults to "GET"
46
+ -m, --mode <name> Operating mode for your application,
47
+ defaults to the value of PYJO_MODE or
48
+ "development"
49
+ -o, --connect-timeout <seconds> Connect timeout, defaults to the value
50
+ of PYJO_CONNECT_TIMEOUT or 10
51
+ -r, --redirect Follow up to 10 redirects
52
+ -S, --response-size <size> Maximum response size in bytes,
53
+ defaults to 2147483648 (2GiB)
54
+ -u, --user <userinfo> Alternate mechanism for specifying
55
+ colon-separated username and password
56
+ -v, --verbose Print request and response headers to
57
+ STDERR
58
+
59
+ :mod:`Pyjoyment.Command.get` performs requests to remote hosts or local
60
+ applications.
61
+
62
+ This is a core command, that means it is always enabled and its code a good
63
+ example for learning to build new commands, you're welcome to fork it.
64
+
65
+ See :mod:`Pyjoyment.Commands` for a list of commands that are available by
66
+ default.
67
+
68
+ See also
69
+ --------
70
+
71
+ :mod:`Pyjoyment`, https://mojolicious.org.
72
+
73
+ Ported from `Mojolicious::Command::get
74
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Command/get.pm>`_ of Mojolicious 9.49.
75
+
76
+ Classes
77
+ -------
78
+ """
79
+
80
+ import Pyjo.Collection
81
+ import Pyjo.DOM
82
+ import Pyjo.Exception
83
+ import Pyjo.IOLoop
84
+ import Pyjo.JSON.Pointer
85
+ import Pyjo.Message
86
+ import Pyjo.URL
87
+ import Pyjo.UserAgent
88
+ import Pyjoyment.Command
89
+
90
+ from Pyjo.JSON import j, to_json
91
+ from Pyjo.Util import decode, extract_usage, getopt, warn
92
+
93
+ import re
94
+ import select
95
+ import sys
96
+ import weakref
97
+
98
+ from typing import Any, cast
99
+
100
+
101
+ class Pyjoyment_Command_get(Pyjoyment.Command.object):
102
+ """
103
+ :mod:`Pyjoyment.Command.get` inherits all attributes and methods from
104
+ :mod:`Pyjoyment.Command` and implements the following new ones.
105
+ """
106
+
107
+ description = 'Perform HTTP request'
108
+ """::
109
+
110
+ description = get.description
111
+ get.description = 'Foo'
112
+
113
+ Short description of this command, used for the command list.
114
+ """
115
+
116
+ usage = extract_usage()
117
+ """::
118
+
119
+ usage = get.usage
120
+ get.usage = 'Foo'
121
+
122
+ Usage information for this command, used for the help screen.
123
+ """
124
+
125
+ def run(self, *args: str) -> None:
126
+ """::
127
+
128
+ get.run(*sys.argv[1:])
129
+
130
+ Run this command.
131
+ """
132
+ # Data from STDIN
133
+ content = _stdin()
134
+
135
+ ua: Any = Pyjo.UserAgent.new(ioloop=Pyjo.IOLoop.singleton)
136
+ arguments = list(args)
137
+ form: dict[str, list[Any]] = {}
138
+ headers: list[str] = []
139
+ opts = getopt(
140
+ arguments,
141
+ {
142
+ 'C|charset=s': None,
143
+ 'c|content=s': None,
144
+ 'f|form=s': lambda name, value: _form(form, value),
145
+ 'H|header=s': headers,
146
+ 'i|inactivity-timeout=i': lambda name, value: setattr(ua, 'inactivity_timeout', value),
147
+ 'k|insecure': lambda name, value: setattr(ua, 'insecure', True),
148
+ 'M|method=s': None,
149
+ 'o|connect-timeout=i': lambda name, value: setattr(ua, 'connect_timeout', value),
150
+ 'r|redirect': None,
151
+ 'S|response-size=i': lambda name, value: setattr(ua, 'max_response_size', value),
152
+ 'u|user=s': None,
153
+ 'v|verbose': None,
154
+ },
155
+ )
156
+ if not opts:
157
+ raise Pyjo.Exception.new(self.usage)
158
+ if opts.content is not None:
159
+ content = opts.content
160
+ method: str = opts.method or 'GET'
161
+ redirect, verbose = opts.redirect, opts.verbose
162
+
163
+ # Left out from Mojolicious: decoding the arguments, which Python has as characters already
164
+ if not arguments:
165
+ raise Pyjo.Exception.new(self.usage)
166
+ url: Any = arguments.pop(0)
167
+ selector = arguments.pop(0) if arguments else None
168
+
169
+ # Parse header pairs
170
+ pairs = {m[1]: m[2] for header in headers if (m := re.match(r'^\s*([^:]+)\s*:\s*(.*)$', header))}
171
+
172
+ # Detect proxy for absolute URLs
173
+ if not url.startswith('/'):
174
+ ua.proxy.detect()
175
+ else:
176
+ ua.server.app = self.app
177
+ if opts.user:
178
+ url = Pyjo.URL.new(url).set(userinfo=opts.user)
179
+ if redirect:
180
+ ua.max_redirects = 10
181
+
182
+ buffer = bytearray()
183
+
184
+ @ua.on('start')
185
+ def start(ua: Any, tx: Any) -> None:
186
+ # Verbose
187
+ tx = weakref.proxy(tx)
188
+ if verbose:
189
+
190
+ @tx.res.content.on('body')
191
+ def body(content: Any) -> None:
192
+ warn(_header(tx.req), _header(tx.res))
193
+
194
+ # Stream content (ignore redirects)
195
+ @tx.res.content.unsubscribe('read').on('read')
196
+ def read(content: Any, chunk: bytes) -> None:
197
+ if redirect and tx.res.is_redirect:
198
+ return
199
+ if selector is not None:
200
+ buffer.extend(chunk)
201
+ else:
202
+ _write(chunk)
203
+
204
+ # Switch to verbose for HEAD requests
205
+ if method == 'HEAD':
206
+ verbose = True
207
+ if form:
208
+ tx = ua.start(ua.build_tx(method, url, pairs, form=form))
209
+ elif content is not None:
210
+ tx = ua.start(ua.build_tx(method, url, pairs, content))
211
+ else:
212
+ tx = ua.start(ua.build_tx(method, url, pairs))
213
+ res = tx.result
214
+
215
+ # JSON Pointer
216
+ if selector is None:
217
+ return None
218
+ if not selector or selector.startswith('/'):
219
+ return _json(bytes(buffer), selector)
220
+
221
+ # Selector
222
+ charset = opts.charset or res.content.charset or res.default_charset
223
+ return _select(bytes(buffer), selector, charset, *arguments)
224
+
225
+
226
+ def _form(form: dict[str, list[Any]], value: str) -> None:
227
+ if m := re.match(r'^(.+)=(@?)(.+)$', value, re.S):
228
+ form.setdefault(m[1], []).append({'file': m[3]} if m[2] else m[3])
229
+
230
+
231
+ def _header(msg: Pyjo.Message.object) -> str:
232
+ return msg.build_start_line().decode('latin-1') + msg.headers.to_str() + '\n\n'
233
+
234
+
235
+ def _json(buffer: bytes, pointer: str) -> None:
236
+ if not (data := j(buffer)):
237
+ return
238
+ if (data := Pyjo.JSON.Pointer.new(data).get(pointer)) is None:
239
+ return
240
+ _say(to_json(cast(object, data)) if isinstance(data, (dict, list)) else data)
241
+
242
+
243
+ def _say(*values: Any) -> None:
244
+ for value in values:
245
+ if value is not None and (text := str(value)):
246
+ print(text)
247
+
248
+
249
+ def _select(buffer: bytes, selector: str, charset: str | None, *args: str) -> None:
250
+ arguments = list(args)
251
+
252
+ # Keep a strong reference to the root
253
+ text = (decode(charset, buffer) if charset else None) or buffer.decode('latin-1')
254
+ dom = Pyjo.DOM.new(text)
255
+ results: Any = dom.find(selector)
256
+
257
+ while arguments:
258
+ command = arguments.pop(0)
259
+
260
+ # Number
261
+ if re.fullmatch(r'\d+', command):
262
+ index = int(command)
263
+ results = Pyjo.Collection.c(*results[index : index + 1])
264
+ continue
265
+
266
+ # Text
267
+ if command == 'text':
268
+ return _say(*results.map('text').each())
269
+
270
+ # All text
271
+ if command == 'all':
272
+ return _say(*results.map('all_text').each())
273
+
274
+ # Attribute
275
+ if command == 'attr':
276
+ return _say(*results.map('attr', arguments[0] if arguments else '').each())
277
+
278
+ # Unknown
279
+ raise Pyjo.Exception.new(f'Unknown command "{command}".\n')
280
+
281
+ _say(*results.each())
282
+
283
+
284
+ # Data from sys.stdin, unless it is a terminal or has nothing to read yet
285
+ def _stdin() -> str | None:
286
+ try:
287
+ if sys.stdin.isatty() or not select.select([sys.stdin], [], [], 0)[0]:
288
+ return None
289
+ except (AttributeError, OSError, ValueError):
290
+ return None
291
+ return sys.stdin.read()
292
+
293
+
294
+ # The bytes of the response, as Mojo prints them, after what sys.stdout has buffered
295
+ def _write(chunk: bytes) -> None:
296
+ sys.stdout.flush()
297
+ if (out := getattr(sys.stdout, 'buffer', None)) is not None:
298
+ out.write(chunk)
299
+ out.flush()
300
+ else:
301
+ sys.stdout.write(chunk.decode('latin-1'))
302
+
303
+
304
+ new = Pyjoyment_Command_get.new
305
+ object = Pyjoyment_Command_get
@@ -0,0 +1,117 @@
1
+ """
2
+ Pyjoyment.Command.lambda - AWS Lambda command
3
+ =============================================
4
+ ::
5
+
6
+ Usage: APPLICATION lambda [OPTIONS]
7
+
8
+ handler = app.start('lambda')
9
+
10
+ Options:
11
+ -h, --help Show this summary of available options
12
+ --home <path> Path to home directory of your application,
13
+ defaults to the value of PYJO_HOME or
14
+ auto-detection
15
+ --log-format <name> Format of log messages, "text" or "json",
16
+ defaults to the value of PYJO_LOG_FORMAT or
17
+ "text"
18
+ -m, --mode <name> Operating mode for your application, defaults to
19
+ the value of PYJO_MODE or "development"
20
+
21
+ :mod:`Pyjoyment.Command.lambda` starts applications with the
22
+ :mod:`Pyjo.Server.Lambda` backend: it returns the handler of an event
23
+ function of AWS Lambda, which a script keeps for the runtime that imports it.
24
+ The script of an application that ends with ``handler = app.start()``, as
25
+ ``myapp.py`` with the handler ``myapp.handler``, runs this command in the
26
+ runtime of AWS Lambda, which has the ``AWS_LAMBDA_RUNTIME_API`` environment
27
+ variable, and the command of its arguments otherwise.
28
+
29
+ The script that runs as the main program with that variable, as the
30
+ ``bootstrap`` of a custom runtime or the command of an image, is the runtime
31
+ itself: the command runs the application for the invocations of the Runtime
32
+ API with :meth:`Pyjo.Server.Lambda.Pyjo_Server_Lambda.run_runtime`, until the
33
+ process ends, where a runtime that imports the script takes its handler. ::
34
+
35
+ #!/bin/sh
36
+ # The bootstrap of a custom runtime, as provided.al2023
37
+ exec python3 "$LAMBDA_TASK_ROOT/myapp.py"
38
+
39
+ This is a core command, that means it is always enabled and its code a good
40
+ example for learning to build new commands, you're welcome to fork it.
41
+
42
+ See :mod:`Pyjoyment.Commands` for a list of commands that are available by
43
+ default.
44
+
45
+ See also
46
+ --------
47
+
48
+ :mod:`Pyjoyment`, https://mojolicious.org.
49
+
50
+ Classes
51
+ -------
52
+ """
53
+
54
+ # Not in Mojolicious: the command of AWS Lambda, as Pyjoyment.Command.wsgi is the one of WSGI; its module has the name
55
+ # of a keyword of Python, which only the loader of the commands imports
56
+
57
+ import Pyjo.Server.Lambda
58
+ import Pyjoyment.Command
59
+
60
+ from Pyjo.Util import extract_usage
61
+
62
+ import inspect
63
+ import os
64
+
65
+ from typing import Any
66
+
67
+
68
+ class Pyjoyment_Command_lambda(Pyjoyment.Command.object):
69
+ """
70
+ :mod:`Pyjoyment.Command.lambda` inherits all attributes and methods from
71
+ :mod:`Pyjoyment.Command` and implements the following new ones.
72
+ """
73
+
74
+ description = 'Start application with AWS Lambda'
75
+ """::
76
+
77
+ description = command.description
78
+ command.description = 'Foo'
79
+
80
+ Short description of this command, used for the command list.
81
+ """
82
+
83
+ usage = extract_usage()
84
+ """::
85
+
86
+ usage = command.usage
87
+ command.usage = 'Foo'
88
+
89
+ Usage information for this command, used for the help screen.
90
+ """
91
+
92
+ def run(self, *args: str) -> Any:
93
+ """::
94
+
95
+ handler = command.run()
96
+
97
+ Run this command.
98
+ """
99
+ server = Pyjo.Server.Lambda.new(app=self.app)
100
+
101
+ # The script of the application as the runtime, where a runtime that imports it takes its handler
102
+ if os.environ.get('AWS_LAMBDA_RUNTIME_API') and _is_main():
103
+ return server.run_runtime()
104
+ return server.to_lambda_app()
105
+
106
+
107
+ # The module that starts the application is the main program, outside of the frames of Pyjoyment, where it is a module
108
+ # that a runtime imports otherwise
109
+ def _is_main() -> bool:
110
+ frame = inspect.currentframe()
111
+ while frame is not None and str(frame.f_globals.get('__name__', '')).split('.')[0] in ('Pyjo', 'Pyjoyment'):
112
+ frame = frame.f_back
113
+ return frame is not None and frame.f_globals.get('__name__') == '__main__'
114
+
115
+
116
+ new = Pyjoyment_Command_lambda.new
117
+ object = Pyjoyment_Command_lambda