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,514 @@
1
+ """
2
+ Pyjoyment.Command.Author.generate.kubernetes - Kubernetes generator command
3
+ ===========================================================================
4
+ ::
5
+
6
+ Usage: APPLICATION generate kubernetes [OPTIONS] [-- SERVER OPTIONS]
7
+
8
+ ./myapp.py generate kubernetes
9
+ python -m MyApp generate kubernetes
10
+ python -m MyApp generate kubernetes --image ghcr.io/me/myapp:1.0
11
+ python -m MyApp generate kubernetes --host-path
12
+ python -m MyApp generate kubernetes --graceful-timeout 25
13
+ python -m MyApp generate kubernetes --shutdown-delay 10
14
+ python -m MyApp generate kubernetes --no-dev
15
+ python -m MyApp generate kubernetes --server prefork -- --workers 2
16
+
17
+ Options:
18
+ --dev Install the dependency group "dev" of
19
+ the project in the pod of "dev", which
20
+ --no-dev leaves out, defaults to
21
+ installing it
22
+ -G, --graceful-timeout <seconds> Graceful timeout of the workers of the
23
+ server of "prod", defaults to the one of
24
+ the server
25
+ -h, --help Show this summary of available options
26
+ --host-path Mount the directory of the project in
27
+ the pod of "dev", in the place of a
28
+ ConfigMap
29
+ -i, --image <name> Image of "prod", the one of "generate
30
+ dockerfile", defaults to the name of the
31
+ application
32
+ -p, --port <port> Port that the server listens on,
33
+ defaults to 3000
34
+ -S, --shutdown-delay <seconds> Time that the server of "prod" keeps
35
+ serving after TERM, defaults to 5
36
+ -s, --server <name> Server of "dev", "daemon", "prefork" or
37
+ "reload", defaults to "reload" with
38
+ --host-path or "daemon"
39
+
40
+ :mod:`Pyjoyment.Command.Author.generate.kubernetes` generates the manifests
41
+ of an application for Kubernetes, as Kustomize has them, in ``kubernetes``: a
42
+ base, with a Deployment and a Service of the name of the application, and two
43
+ overlays, ``dev`` and ``prod``, which ``kubectl apply -k`` applies. ::
44
+
45
+ kubernetes/
46
+ base/
47
+ kustomization.yaml
48
+ deployment.yaml
49
+ probes.yaml
50
+ service.yaml
51
+ dev/
52
+ kustomization.yaml
53
+ deployment.yaml
54
+ files/
55
+ prod/
56
+ kustomization.yaml
57
+ deployment.yaml
58
+
59
+ The server listens on the port of the ``PORT`` environment variable, ``3000``
60
+ by default, as in the image of
61
+ :mod:`Pyjoyment.Command.Author.generate.dockerfile`, which the Service exposes
62
+ on the port ``80``. The probes of Kubernetes of an application with
63
+ :mod:`Pyjoyment.Plugin.Health` request it: the startup and the liveness its
64
+ liveness, ``/livez`` by default, and the readiness its readiness,
65
+ ``/readyz``, which fails while the server stops gracefully, so that the
66
+ Service sends no more requests to the pod before its end. The probes of an
67
+ application without it connect to the port, which the server listens on once
68
+ it has started. The probes are a patch of their own, ``base/probes.yaml``, as
69
+ the application gets the plugin or loses it.
70
+
71
+ The overlay ``prod`` runs the image of ``--image``, which
72
+ :mod:`Pyjoyment.Command.Author.generate.dockerfile` builds, with the
73
+ pre-forking server of the image, in the operating mode ``production``.
74
+ Kubernetes takes a pod that it stops out of the endpoints of its Service at
75
+ the same time as it sends it ``TERM``, so the server keeps serving for the 5
76
+ seconds of ``--shutdown-delay``, which sets ``PYJO_SHUTDOWN_DELAY``, until the
77
+ proxies of the cluster know it, and ``terminationGracePeriodSeconds`` is 5
78
+ seconds above the delay and the 25 seconds that
79
+ :attr:`Pyjo.Server.Prefork.Pyjo_Server_Prefork.graceful_timeout` gives a
80
+ worker, or ``--graceful-timeout``, which sets ``PYJO_GRACEFUL_TIMEOUT``, so
81
+ that the server stops by itself before the kill of Kubernetes. Without
82
+ ``--image``, the image is the one of the name of the application, as
83
+ ``docker build -t myapp .`` tags it, which a cluster of Docker Desktop or
84
+ OrbStack takes from Docker.
85
+
86
+ The overlay ``dev`` runs the application in the image of uv with Python, of
87
+ the version that runs the generator, which installs the dependencies of the
88
+ application when the pod starts, as the image of
89
+ :mod:`Pyjoyment.Command.Author.generate.dockerfile` does when it is built, in
90
+ an ``emptyDir``, with the dependency group ``dev`` of a project, which
91
+ ``--no-dev`` leaves out, and runs it in the operating mode ``development``,
92
+ without building an image, with the server of ``--server``: the command
93
+ ``daemon``, ``prefork``, or ``pyjo-reload`` of :mod:`Pyjo.Server.Reload`,
94
+ with the options after ``--``. The application is a ConfigMap that ``configMapGenerator`` of Kustomize makes of a
95
+ copy of it in ``kubernetes/dev/files``, which the pod copies, as the files of a
96
+ ConfigMap are read-only. The application is the files of the current
97
+ directory, without hidden ones, ``__pycache__``, ``dist`` and ``kubernetes``,
98
+ as for :mod:`Pyjoyment.Command.Author.generate.lambda_zip`, and a ConfigMap
99
+ holds 1 MiB at most. A new ConfigMap of a changed application has a new name,
100
+ which rolls the Deployment out. ``kubernetes/dev/files`` is a build of the
101
+ application, as ``dist`` of
102
+ :mod:`Pyjoyment.Command.Author.generate.lambda_zip`, which goes into
103
+ ``.gitignore``.
104
+
105
+ With ``--host-path``, the pod of ``dev`` mounts the directory of the project
106
+ in the place of the ConfigMap, as a cluster that sees the disk of the
107
+ machine, as the one of Docker Desktop or OrbStack, or of kind with
108
+ ``extraMounts``, mounts it, and the server is ``pyjo-reload``, which restarts
109
+ the application when a file of it changes, where a ConfigMap changes with a
110
+ new pod and the server is ``daemon``.
111
+
112
+ The files of ``base`` and ``prod``, which are to be edited, are written once,
113
+ and the ones of ``dev`` and ``base/probes.yaml`` anew each time, as the
114
+ application changes, which a patch of its own in the place of the one of
115
+ ``base/probes.yaml`` in ``base/kustomization.yaml`` keeps from changing the
116
+ probes.
117
+
118
+ This is a core command, that means it is always enabled and its code a good
119
+ example for learning to build new commands, you're welcome to fork it.
120
+
121
+ See :mod:`Pyjoyment.Commands` for a list of commands that are available by
122
+ default.
123
+
124
+ See also
125
+ --------
126
+
127
+ :mod:`Pyjoyment`, https://mojolicious.org.
128
+
129
+ It has no counterpart in Mojolicious.
130
+
131
+ Classes
132
+ -------
133
+ """
134
+
135
+ import Pyjo.Exception
136
+ import Pyjo.File
137
+ import Pyjoyment.Command
138
+
139
+ from Pyjo.Util import extract_usage, getopt
140
+
141
+ import json
142
+ import os
143
+ import re
144
+ import shlex
145
+ import shutil
146
+ import sys
147
+
148
+ from typing import Any
149
+
150
+
151
+ # The data of a ConfigMap, at most
152
+ _LIMIT = 1024 * 1024
153
+
154
+
155
+ class Pyjoyment_Command_Author_generate_kubernetes(Pyjoyment.Command.object):
156
+ """
157
+ :mod:`Pyjoyment.Command.Author.generate.kubernetes` inherits all
158
+ attributes and methods from :mod:`Pyjoyment.Command` and implements the
159
+ following new ones.
160
+ """
161
+
162
+ description = 'Generate the manifests of Kubernetes'
163
+ """::
164
+
165
+ description = kubernetes.description
166
+ kubernetes.description = 'Foo'
167
+
168
+ Short description of this command, used for the command list.
169
+ """
170
+
171
+ usage = extract_usage()
172
+ """::
173
+
174
+ usage = kubernetes.usage
175
+ kubernetes.usage = 'Foo'
176
+
177
+ Usage information for this command, used for the help screen.
178
+ """
179
+
180
+ def run(self, *args: str) -> None:
181
+ """::
182
+
183
+ kubernetes.run(*sys.argv[1:])
184
+
185
+ Run this command.
186
+ """
187
+ arguments = list(args)
188
+ spec = {'dev!': None, 'G|graceful-timeout=i': None, 'host-path': None, 'i|image=s': None, 'p|port=i': None}
189
+ if not (opts := getopt(arguments, {**spec, 'S|shutdown-delay=i': None, 's|server=s': None})):
190
+ raise Pyjo.Exception.new(self.usage)
191
+
192
+ # The server of dev, which reloads the application of the directory of the project, where its files change,
193
+ # and the arguments after "--" for it
194
+ server: str = opts.server or ('reload' if opts.host_path else 'daemon')
195
+ if server not in ('daemon', 'prefork', 'reload'):
196
+ raise Pyjo.Exception.new(f'Unknown server: {server}')
197
+
198
+ # The name of the resources, which Kubernetes takes in lower case, with "-" in the place of "_", and the one of
199
+ # the image, as "docker build -t" tags it
200
+ moniker = self.app.moniker
201
+ name = re.sub(r'[^a-z0-9]+', '-', moniker.lower()).strip('-') or 'app'
202
+
203
+ # The probes of Pyjoyment.Plugin.Health, or of the port without it; helpers of a plugin are found at run time, so
204
+ # the application is Any
205
+ app: Any = self.app
206
+ health = app.renderer.get_helper('health.path') is not None
207
+ probes = {'livez': app.health.path('livez'), 'readyz': app.health.path('readyz')} if health else None
208
+
209
+ # The script of Pyjoyment.Lite or the package of the application, and the dependencies of the inline metadata
210
+ # of PEP 723 of the script, if it has them and there is no pyproject.toml, as generate dockerfile has them
211
+ exe = os.environ.get('PYJO_EXE')
212
+ script = Pyjo.File.path(exe).to_rel(self.app.home).to_str().replace(os.sep, '/') if exe else None
213
+ package = type(self.app).__module__
214
+ project = os.path.isfile(self.rel_file('pyproject.toml')) or not exe or not _metadata(Pyjo.File.path(exe))
215
+ lock = 'uv.lock' if project else f'{script}.lock'
216
+ port = opts.port or 3000
217
+
218
+ base = {'moniker': moniker, 'name': name, 'port': port, 'probes': probes}
219
+ self.render_to_rel_file('base_kustomization', 'kubernetes/base/kustomization.yaml', base)
220
+ self.render_to_rel_file('base_deployment', 'kubernetes/base/deployment.yaml', base)
221
+ self.render_to_rel_file('base_service', 'kubernetes/base/service.yaml', base)
222
+ # The probes anew each time, as the application gets or loses the plugin Health
223
+ self._write(self.rel_file('kubernetes/base/probes.yaml'), self.render_data('base_probes', base))
224
+
225
+ graceful: int | None = opts.graceful_timeout
226
+ delay: int = 5 if opts.shutdown_delay is None else opts.shutdown_delay
227
+ prod = {'delay': delay, 'graceful_timeout': graceful, 'image': opts.image, 'moniker': moniker, 'name': name}
228
+ self.render_to_rel_file('prod_kustomization', 'kubernetes/prod/kustomization.yaml', prod)
229
+ self.render_to_rel_file('prod_deployment', 'kubernetes/prod/deployment.yaml', prod)
230
+
231
+ # The command of dev: the dependencies in the emptyDir, with the group "dev" of a project unless --no-dev, and
232
+ # the server, with "python -m", which puts the application in the path, as the image of generate dockerfile
233
+ # does; pyjo-reload watches the package of an application too, as it watches the script, "lib" and "templates"
234
+ # by default
235
+ locked = ' --locked' if os.path.isfile(self.rel_file(lock)) else ''
236
+ if project:
237
+ sync = f'uv sync --no-install-project{locked}{" --no-dev" if opts.dev is False else ""}'
238
+ else:
239
+ sync = f'uv venv --allow-existing /uv/venv && uv sync --script {shlex.quote(str(script))} --active{locked}'
240
+ python = '/uv/venv/bin/python'
241
+ if server == 'reload':
242
+ watch = [] if script else ['-w', package, '-w', 'templates']
243
+ cmd = [python, '-m', 'Pyjo.Server.Reload', *watch, *arguments, script or f'{package}/__main__.py']
244
+ else:
245
+ cmd = [python, *([script] if script else ['-m', package]), server, *arguments]
246
+ command = f'{sync} && exec {shlex.join(cmd)}'
247
+
248
+ # The application in a ConfigMap of a copy of its files, or the directory of the project
249
+ files: list[tuple[str, str, str]] = []
250
+ dev = self.rel_file('kubernetes/dev')
251
+ copy = dev.child('files')
252
+ if os.path.isdir(copy):
253
+ copy.remove_tree()
254
+ if not opts.host_path:
255
+ size = 0
256
+ keys: set[str] = set()
257
+ for file in Pyjo.File.path().list_tree().each():
258
+ path = file.to_rel(Pyjo.File.path()).to_str().replace(os.sep, '/')
259
+ if path.split('/')[0] in ('dist', 'kubernetes') or '__pycache__' in path.split('/'):
260
+ continue
261
+ if (size := size + os.path.getsize(file)) > _LIMIT:
262
+ raise Pyjo.Exception.new(f'The application is larger than a ConfigMap holds: {_LIMIT} bytes')
263
+ key = _key(path, keys)
264
+ files.append((key, json.dumps(f'{key}=files/{path}'), json.dumps(path)))
265
+ destination = copy.child(*path.split('/'))
266
+ destination.dirname().make_path()
267
+ shutil.copyfile(file.to_str(), destination.to_str())
268
+
269
+ dev.make_path()
270
+ values = {
271
+ 'command': json.dumps(['sh', '-c', command]),
272
+ 'files': files,
273
+ 'host_path': json.dumps(Pyjo.File.path().to_abs().to_str()) if opts.host_path else None,
274
+ 'image': f'ghcr.io/astral-sh/uv:python{sys.version_info.major}.{sys.version_info.minor}-trixie-slim',
275
+ 'name': name,
276
+ 'project': project,
277
+ }
278
+ for file in ('kustomization', 'deployment'):
279
+ self._write(dev.child(f'{file}.yaml'), self.render_data(f'dev_{file}', values))
280
+
281
+ # A file that the generator writes anew each time, where write_file keeps one that exists
282
+ def _write(self, file: Pyjo.File.object, data: str) -> None:
283
+ file.spew(data)
284
+ self._loud(f' [write] {file}')
285
+
286
+
287
+ # Whether the script has the inline metadata of PEP 723
288
+ def _metadata(path: Pyjo.File.object) -> bool:
289
+ try:
290
+ return re.search(r'(?m)^# /// script\r?$', str(path.slurp('UTF-8') or '')) is not None
291
+ except OSError:
292
+ return False
293
+
294
+
295
+ # The key of a file in a ConfigMap, which has no "/", unique among the keys of the other files
296
+ def _key(path: str, keys: set[str]) -> str:
297
+ key = re.sub(r'[^-._a-zA-Z0-9]', '-', path.replace('/', '.')).lstrip('.') or 'file'
298
+ unique, n = key, 1
299
+ while unique in keys:
300
+ n += 1
301
+ unique = f'{key}-{n}'
302
+ keys.add(unique)
303
+ return unique
304
+
305
+
306
+ new = Pyjoyment_Command_Author_generate_kubernetes.new
307
+ object = Pyjoyment_Command_Author_generate_kubernetes
308
+
309
+ __DATA__ = r"""
310
+ @@ base_kustomization
311
+ apiVersion: kustomize.config.k8s.io/v1beta1
312
+ kind: Kustomization
313
+ labels:
314
+ - pairs:
315
+ app.kubernetes.io/name: <%= name %>
316
+ includeSelectors: true
317
+ resources:
318
+ - deployment.yaml
319
+ - service.yaml
320
+ patches:
321
+ - path: probes.yaml
322
+
323
+ @@ base_deployment
324
+ apiVersion: apps/v1
325
+ kind: Deployment
326
+ metadata:
327
+ name: <%= name %>
328
+ spec:
329
+ replicas: 1
330
+ template:
331
+ spec:
332
+ containers:
333
+ - name: <%= name %>
334
+ image: <%= moniker %>
335
+ imagePullPolicy: IfNotPresent
336
+ env:
337
+ - name: PORT
338
+ value: "<%= port %>"
339
+ ports:
340
+ - name: http
341
+ containerPort: <%= port %>
342
+
343
+ @@ base_probes
344
+ # Written by "generate kubernetes" anew each time, as the application has the plugin Health or not
345
+ apiVersion: apps/v1
346
+ kind: Deployment
347
+ metadata:
348
+ name: <%= name %>
349
+ spec:
350
+ template:
351
+ spec:
352
+ containers:
353
+ - name: <%= name %>
354
+ startupProbe:
355
+ % if probes:
356
+ httpGet:
357
+ path: <%= probes['livez'] %>
358
+ port: http
359
+ % else:
360
+ tcpSocket:
361
+ port: http
362
+ % end
363
+ periodSeconds: 1
364
+ failureThreshold: 30
365
+ livenessProbe:
366
+ % if probes:
367
+ httpGet:
368
+ path: <%= probes['livez'] %>
369
+ port: http
370
+ % else:
371
+ tcpSocket:
372
+ port: http
373
+ % end
374
+ readinessProbe:
375
+ % if probes:
376
+ httpGet:
377
+ path: <%= probes['readyz'] %>
378
+ port: http
379
+ % else:
380
+ tcpSocket:
381
+ port: http
382
+ % end
383
+ periodSeconds: 2
384
+
385
+ @@ base_service
386
+ apiVersion: v1
387
+ kind: Service
388
+ metadata:
389
+ name: <%= name %>
390
+ spec:
391
+ ports:
392
+ - name: http
393
+ port: 80
394
+ targetPort: http
395
+
396
+ @@ prod_kustomization
397
+ apiVersion: kustomize.config.k8s.io/v1beta1
398
+ kind: Kustomization
399
+ resources:
400
+ - ../base
401
+ patches:
402
+ - path: deployment.yaml
403
+ % if image:
404
+ images:
405
+ - name: <%= moniker %>
406
+ newName: <%= image %>
407
+ % end
408
+
409
+ @@ prod_deployment
410
+ apiVersion: apps/v1
411
+ kind: Deployment
412
+ metadata:
413
+ name: <%= name %>
414
+ spec:
415
+ replicas: 2
416
+ template:
417
+ spec:
418
+ terminationGracePeriodSeconds: <%= delay + (25 if graceful_timeout is None else graceful_timeout) + 5 %>
419
+ containers:
420
+ - name: <%= name %>
421
+ env:
422
+ - name: PYJO_MODE
423
+ value: production
424
+ % if graceful_timeout is not None:
425
+ - name: PYJO_GRACEFUL_TIMEOUT
426
+ value: "<%= graceful_timeout %>"
427
+ % end
428
+ % if delay:
429
+ - name: PYJO_SHUTDOWN_DELAY
430
+ value: "<%= delay %>"
431
+ % end
432
+
433
+ @@ dev_kustomization
434
+ # Written by "generate kubernetes" anew each time
435
+ apiVersion: kustomize.config.k8s.io/v1beta1
436
+ kind: Kustomization
437
+ resources:
438
+ - ../base
439
+ patches:
440
+ - path: deployment.yaml
441
+ % if not host_path:
442
+ configMapGenerator:
443
+ - name: <%= name %>-files
444
+ files:
445
+ % for _, source, _ in files:
446
+ - <%= source %>
447
+ % end
448
+ % end
449
+
450
+ @@ dev_deployment
451
+ # Written by "generate kubernetes" anew each time
452
+ apiVersion: apps/v1
453
+ kind: Deployment
454
+ metadata:
455
+ name: <%= name %>
456
+ spec:
457
+ template:
458
+ spec:
459
+ % if not host_path:
460
+ initContainers:
461
+ - name: app
462
+ image: <%= image %>
463
+ command: ["sh", "-c", "cp -RL /files/* /app"]
464
+ volumeMounts:
465
+ - name: files
466
+ mountPath: /files
467
+ - name: app
468
+ mountPath: /app
469
+ % end
470
+ containers:
471
+ - name: <%= name %>
472
+ image: <%= image %>
473
+ workingDir: /app
474
+ command: <%= command %>
475
+ env:
476
+ - name: PYJO_MODE
477
+ value: development
478
+ - name: UV_CACHE_DIR
479
+ value: /uv/cache
480
+ % if project:
481
+ - name: UV_PROJECT_ENVIRONMENT
482
+ value: /uv/venv
483
+ % else:
484
+ - name: VIRTUAL_ENV
485
+ value: /uv/venv
486
+ % end
487
+ startupProbe:
488
+ failureThreshold: 300
489
+ volumeMounts:
490
+ - name: app
491
+ mountPath: /app
492
+ - name: uv
493
+ mountPath: /uv
494
+ volumes:
495
+ % if host_path:
496
+ - name: app
497
+ hostPath:
498
+ path: <%= host_path %>
499
+ type: Directory
500
+ % else:
501
+ - name: files
502
+ configMap:
503
+ name: <%= name %>-files
504
+ items:
505
+ % for key, _, path in files:
506
+ - key: <%= key %>
507
+ path: <%= path %>
508
+ % end
509
+ - name: app
510
+ emptyDir: {}
511
+ % end
512
+ - name: uv
513
+ emptyDir: {}
514
+ """