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.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/Asset/File.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.Asset.File - File storage for HTTP content
|
|
3
|
+
===============================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Asset.File import File
|
|
7
|
+
|
|
8
|
+
# Temporary file
|
|
9
|
+
file = File()
|
|
10
|
+
file.add_chunk(b'foo bar baz')
|
|
11
|
+
if file.contains(b'bar') >= 0:
|
|
12
|
+
print('File contains "bar"')
|
|
13
|
+
print(file.slurp())
|
|
14
|
+
|
|
15
|
+
# Or import the module, as in Google Python Style Guide
|
|
16
|
+
|
|
17
|
+
import Pyjo.Asset.File
|
|
18
|
+
|
|
19
|
+
# Temporary file
|
|
20
|
+
file = Pyjo.Asset.File.new()
|
|
21
|
+
file.add_chunk(b'foo bar baz')
|
|
22
|
+
if file.contains(b'bar') >= 0:
|
|
23
|
+
print('File contains "bar"')
|
|
24
|
+
print(file.slurp())
|
|
25
|
+
|
|
26
|
+
# Existing file
|
|
27
|
+
file = Pyjo.Asset.File.new(path='/home/sri/foo.txt')
|
|
28
|
+
file.move_to('/yada.txt')
|
|
29
|
+
print(file.slurp())
|
|
30
|
+
|
|
31
|
+
:mod:`Pyjo.Asset.File` is a file storage backend for HTTP content.
|
|
32
|
+
|
|
33
|
+
The end of a ``with`` block closes the file, and deletes it with
|
|
34
|
+
:attr:`~Pyjo_Asset_File.cleanup`.
|
|
35
|
+
|
|
36
|
+
Events
|
|
37
|
+
------
|
|
38
|
+
|
|
39
|
+
:mod:`Pyjo.Asset.File` inherits all events from :mod:`Pyjo.Asset`.
|
|
40
|
+
|
|
41
|
+
See also
|
|
42
|
+
--------
|
|
43
|
+
|
|
44
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
45
|
+
|
|
46
|
+
Ported from `Mojo::Asset::File <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Asset/File.pm>`_
|
|
47
|
+
of Mojolicious 9.49.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
import Pyjo.Asset
|
|
51
|
+
import Pyjo.Exception
|
|
52
|
+
import Pyjo.File
|
|
53
|
+
|
|
54
|
+
import atexit
|
|
55
|
+
import builtins
|
|
56
|
+
import os
|
|
57
|
+
import sys
|
|
58
|
+
import tempfile
|
|
59
|
+
import weakref
|
|
60
|
+
|
|
61
|
+
from types import TracebackType
|
|
62
|
+
from typing import IO, Self
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
type Anything = builtins.object
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Pyjo_Asset_File(Pyjo.Asset.object):
|
|
69
|
+
"""
|
|
70
|
+
:mod:`Pyjo.Asset.File` inherits all methods from :mod:`Pyjo.Asset` and
|
|
71
|
+
implements the following new ones.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
*,
|
|
77
|
+
cleanup: bool | None = None,
|
|
78
|
+
end_range: int | None = None,
|
|
79
|
+
handle: IO[bytes] | None = None,
|
|
80
|
+
path: str | os.PathLike[str] | None = None,
|
|
81
|
+
start_range: int = 0,
|
|
82
|
+
tmpdir: str | os.PathLike[str] | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""::
|
|
85
|
+
|
|
86
|
+
file = Pyjo.Asset.File.new()
|
|
87
|
+
file = Pyjo.Asset.File.new(path='/home/sri/test.txt')
|
|
88
|
+
|
|
89
|
+
Construct a new :mod:`Pyjo.Asset.File` object.
|
|
90
|
+
"""
|
|
91
|
+
super().__init__(end_range=end_range, start_range=start_range)
|
|
92
|
+
|
|
93
|
+
self.cleanup = cleanup
|
|
94
|
+
"""::
|
|
95
|
+
|
|
96
|
+
boolean = file.cleanup
|
|
97
|
+
file.cleanup = boolean
|
|
98
|
+
|
|
99
|
+
Delete :attr:`path` automatically once the file is not used anymore.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
self.path = None if path is None else os.fspath(path)
|
|
103
|
+
"""::
|
|
104
|
+
|
|
105
|
+
path = file.path
|
|
106
|
+
file.path = '/home/sri/foo.txt'
|
|
107
|
+
|
|
108
|
+
File path used to create :attr:`handle`.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
self._handle = handle
|
|
112
|
+
self._pid = os.getpid()
|
|
113
|
+
self._tmpdir = None if tmpdir is None else os.fspath(tmpdir)
|
|
114
|
+
_FILES.add(self)
|
|
115
|
+
|
|
116
|
+
def __del__(self) -> None:
|
|
117
|
+
# Module globals may be gone at the end of the interpreter
|
|
118
|
+
try:
|
|
119
|
+
if not sys.is_finalizing():
|
|
120
|
+
self.close()
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
# Not in Mojolicious: a context manager closes the file where it ends, as PyPy frees the object only later
|
|
125
|
+
def __enter__(self) -> Self:
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def __exit__(
|
|
129
|
+
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
130
|
+
) -> None:
|
|
131
|
+
self.close()
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def handle(self) -> IO[bytes]:
|
|
135
|
+
"""::
|
|
136
|
+
|
|
137
|
+
handle = file.handle
|
|
138
|
+
file.handle = open('/home/sri/foo.txt', 'rb')
|
|
139
|
+
|
|
140
|
+
Filehandle, created on demand for :attr:`path`, which can be
|
|
141
|
+
generated automatically and safely based on :attr:`tmpdir`.
|
|
142
|
+
"""
|
|
143
|
+
if self._handle is not None:
|
|
144
|
+
return self._handle
|
|
145
|
+
|
|
146
|
+
# Open existing file
|
|
147
|
+
path = self.path
|
|
148
|
+
if path is not None and os.path.exists(path):
|
|
149
|
+
self._handle = Pyjo.File.new(path).open('rb', buffering=0)
|
|
150
|
+
return self._handle
|
|
151
|
+
|
|
152
|
+
if self.cleanup is None:
|
|
153
|
+
self.cleanup = True
|
|
154
|
+
|
|
155
|
+
# Create a temporary file
|
|
156
|
+
if path is None:
|
|
157
|
+
fd, path = tempfile.mkstemp(prefix='pyjo.tmp.', dir=self.tmpdir)
|
|
158
|
+
os.close(fd)
|
|
159
|
+
self.path = path
|
|
160
|
+
|
|
161
|
+
# Create a specific file, appended to as Perl's +>> does
|
|
162
|
+
self._handle = Pyjo.File.new(path).open('a+b', buffering=0)
|
|
163
|
+
return self._handle
|
|
164
|
+
|
|
165
|
+
@handle.setter
|
|
166
|
+
def handle(self, value: IO[bytes]) -> None:
|
|
167
|
+
self._handle = value
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def tmpdir(self) -> str:
|
|
171
|
+
"""::
|
|
172
|
+
|
|
173
|
+
tmpdir = file.tmpdir
|
|
174
|
+
file.tmpdir = '/tmp'
|
|
175
|
+
|
|
176
|
+
Temporary directory used to generate :attr:`path`, defaults to the
|
|
177
|
+
value of the ``PYJO_TMPDIR`` environment variable or auto-detection.
|
|
178
|
+
"""
|
|
179
|
+
if self._tmpdir is None:
|
|
180
|
+
self._tmpdir = os.environ.get('PYJO_TMPDIR') or tempfile.gettempdir()
|
|
181
|
+
return self._tmpdir
|
|
182
|
+
|
|
183
|
+
@tmpdir.setter
|
|
184
|
+
def tmpdir(self, value: str | os.PathLike[str]) -> None:
|
|
185
|
+
self._tmpdir = os.fspath(value)
|
|
186
|
+
|
|
187
|
+
def add_chunk(self, chunk: bytes) -> Self:
|
|
188
|
+
"""::
|
|
189
|
+
|
|
190
|
+
file = file.add_chunk(b'foo bar baz')
|
|
191
|
+
|
|
192
|
+
Add chunk of data.
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
written = self.handle.write(chunk)
|
|
196
|
+
except OSError as e:
|
|
197
|
+
error = type(e)(f"Can't write to asset: {e.strerror}" if e.strerror else "Can't write to asset")
|
|
198
|
+
error.errno = e.errno
|
|
199
|
+
raise error from e
|
|
200
|
+
if written != len(chunk):
|
|
201
|
+
raise Pyjo.Exception.new("Can't write to asset: incomplete write")
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
# Not in Mojolicious: what DESTROY does, which Python does when it frees the file, PyPy only later
|
|
205
|
+
def close(self) -> None:
|
|
206
|
+
"""::
|
|
207
|
+
|
|
208
|
+
file.close()
|
|
209
|
+
|
|
210
|
+
Close :attr:`handle` and delete :attr:`path` with :attr:`cleanup`, as
|
|
211
|
+
it happens when the file is freed.
|
|
212
|
+
"""
|
|
213
|
+
if (handle := self._handle) is not None:
|
|
214
|
+
handle.close()
|
|
215
|
+
if not self.cleanup or (path := self.path) is None:
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Only the process that created the file is allowed to remove it
|
|
219
|
+
if os.access(path, os.W_OK) and self._pid == os.getpid():
|
|
220
|
+
Pyjo.File.new(path).remove()
|
|
221
|
+
|
|
222
|
+
def contains(self, string: bytes) -> int:
|
|
223
|
+
"""::
|
|
224
|
+
|
|
225
|
+
position = file.contains(b'bar')
|
|
226
|
+
|
|
227
|
+
Check if asset contains a specific string.
|
|
228
|
+
"""
|
|
229
|
+
handle = self.handle
|
|
230
|
+
handle.seek(self.start_range, os.SEEK_SET)
|
|
231
|
+
|
|
232
|
+
# Calculate window size
|
|
233
|
+
end = self.end_range if self.end_range is not None else self.size
|
|
234
|
+
length = len(string)
|
|
235
|
+
size = length if length > 131072 else 131072
|
|
236
|
+
if size > end - self.start_range:
|
|
237
|
+
size = end - self.start_range
|
|
238
|
+
|
|
239
|
+
# Sliding window search
|
|
240
|
+
offset = 0
|
|
241
|
+
window = handle.read(length) or b''
|
|
242
|
+
start = len(window)
|
|
243
|
+
while offset < end:
|
|
244
|
+
# Read as much as possible
|
|
245
|
+
diff = end - (start + offset)
|
|
246
|
+
buffer = handle.read(diff if diff < size else size) or b''
|
|
247
|
+
read = len(buffer)
|
|
248
|
+
window += buffer
|
|
249
|
+
|
|
250
|
+
# Search window
|
|
251
|
+
if (pos := window.find(string)) >= 0:
|
|
252
|
+
return offset + pos
|
|
253
|
+
offset += read
|
|
254
|
+
if read == 0 or offset == end:
|
|
255
|
+
return -1
|
|
256
|
+
|
|
257
|
+
# Resize window
|
|
258
|
+
window = window[read:]
|
|
259
|
+
|
|
260
|
+
return -1
|
|
261
|
+
|
|
262
|
+
def get_chunk(self, offset: int, max: int | None = None) -> bytes:
|
|
263
|
+
"""::
|
|
264
|
+
|
|
265
|
+
chunk = file.get_chunk(offset)
|
|
266
|
+
chunk = file.get_chunk(offset, max)
|
|
267
|
+
|
|
268
|
+
Get chunk of data starting from a specific position, defaults to a
|
|
269
|
+
maximum chunk size of ``131072`` bytes (128KiB).
|
|
270
|
+
"""
|
|
271
|
+
if max is None:
|
|
272
|
+
max = 131072
|
|
273
|
+
|
|
274
|
+
offset += self.start_range
|
|
275
|
+
handle = self.handle
|
|
276
|
+
handle.seek(offset, os.SEEK_SET)
|
|
277
|
+
|
|
278
|
+
if (end := self.end_range) is not None:
|
|
279
|
+
if (chunk := end + 1 - offset) <= 0:
|
|
280
|
+
return b''
|
|
281
|
+
return handle.read(chunk if chunk < max else max) or b''
|
|
282
|
+
return handle.read(max) or b''
|
|
283
|
+
|
|
284
|
+
@property
|
|
285
|
+
def is_file(self) -> bool:
|
|
286
|
+
"""::
|
|
287
|
+
|
|
288
|
+
boolean = file.is_file
|
|
289
|
+
|
|
290
|
+
True, this is a :mod:`Pyjo.Asset.File` object.
|
|
291
|
+
"""
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
def move_to(self, to: Anything) -> Self:
|
|
295
|
+
"""::
|
|
296
|
+
|
|
297
|
+
file = file.move_to('/home/sri/bar.txt')
|
|
298
|
+
|
|
299
|
+
Move asset data into a specific file and disable :attr:`cleanup`.
|
|
300
|
+
"""
|
|
301
|
+
# Windows requires that the handle is closed
|
|
302
|
+
self.handle.close()
|
|
303
|
+
self._handle = None
|
|
304
|
+
|
|
305
|
+
# Move file and prevent clean up
|
|
306
|
+
Pyjo.File.new(self.path).move_to(to)
|
|
307
|
+
self.path = str(to)
|
|
308
|
+
self.cleanup = False
|
|
309
|
+
return self
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def mtime(self) -> float:
|
|
313
|
+
"""::
|
|
314
|
+
|
|
315
|
+
mtime = file.mtime
|
|
316
|
+
|
|
317
|
+
Modification time of asset.
|
|
318
|
+
"""
|
|
319
|
+
return os.fstat(self.handle.fileno()).st_mtime
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def size(self) -> int:
|
|
323
|
+
"""::
|
|
324
|
+
|
|
325
|
+
size = file.size
|
|
326
|
+
|
|
327
|
+
Size of asset data in bytes.
|
|
328
|
+
"""
|
|
329
|
+
return os.fstat(self.handle.fileno()).st_size
|
|
330
|
+
|
|
331
|
+
def slurp(self) -> bytes:
|
|
332
|
+
"""::
|
|
333
|
+
|
|
334
|
+
content = file.slurp()
|
|
335
|
+
|
|
336
|
+
Read all asset data at once.
|
|
337
|
+
"""
|
|
338
|
+
handle = self.handle
|
|
339
|
+
handle.seek(0, os.SEEK_SET)
|
|
340
|
+
content = b''
|
|
341
|
+
try:
|
|
342
|
+
while buffer := handle.read(131072):
|
|
343
|
+
content += buffer
|
|
344
|
+
except OSError as e:
|
|
345
|
+
error = type(e)(f"Can't read from asset: {e.strerror}" if e.strerror else "Can't read from asset")
|
|
346
|
+
error.errno = e.errno
|
|
347
|
+
raise error from e
|
|
348
|
+
return content
|
|
349
|
+
|
|
350
|
+
def to_file(self) -> Self:
|
|
351
|
+
"""::
|
|
352
|
+
|
|
353
|
+
file = file.to_file()
|
|
354
|
+
|
|
355
|
+
Does nothing but return the invocant, since we already have a
|
|
356
|
+
:mod:`Pyjo.Asset.File` object.
|
|
357
|
+
"""
|
|
358
|
+
return self
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# Not in Mojolicious: the files that exist, which are closed, and deleted with cleanup, at the end of the interpreter, as
|
|
362
|
+
# Perl does it then, while __del__ does nothing
|
|
363
|
+
_FILES: 'weakref.WeakSet[Pyjo_Asset_File]' = weakref.WeakSet()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@atexit.register
|
|
367
|
+
def _close() -> None:
|
|
368
|
+
for file in list(_FILES):
|
|
369
|
+
try:
|
|
370
|
+
file.close()
|
|
371
|
+
except Exception:
|
|
372
|
+
pass
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
new = Pyjo_Asset_File.new
|
|
376
|
+
object = Pyjo_Asset_File
|
|
377
|
+
File = Pyjo_Asset_File
|
Pyjo/Asset/Memory.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.Asset.Memory - In-memory storage for HTTP content
|
|
3
|
+
======================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Asset.Memory import Memory
|
|
7
|
+
|
|
8
|
+
mem = Memory()
|
|
9
|
+
mem.add_chunk(b'foo bar baz')
|
|
10
|
+
print(mem.slurp())
|
|
11
|
+
|
|
12
|
+
# Or import the module, as in Google Python Style Guide
|
|
13
|
+
|
|
14
|
+
import Pyjo.Asset.Memory
|
|
15
|
+
|
|
16
|
+
mem = Pyjo.Asset.Memory.new()
|
|
17
|
+
mem.add_chunk(b'foo bar baz')
|
|
18
|
+
print(mem.slurp())
|
|
19
|
+
|
|
20
|
+
:mod:`Pyjo.Asset.Memory` is an in-memory storage backend for HTTP content.
|
|
21
|
+
|
|
22
|
+
Events
|
|
23
|
+
------
|
|
24
|
+
|
|
25
|
+
:mod:`Pyjo.Asset.Memory` inherits all events from :mod:`Pyjo.Asset` and can
|
|
26
|
+
emit the following new ones.
|
|
27
|
+
|
|
28
|
+
upgrade
|
|
29
|
+
~~~~~~~
|
|
30
|
+
::
|
|
31
|
+
|
|
32
|
+
mem.on('upgrade', lambda mem, file: ...)
|
|
33
|
+
|
|
34
|
+
Emitted when asset gets upgraded to a :mod:`Pyjo.Asset.File` object. ::
|
|
35
|
+
|
|
36
|
+
mem.on('upgrade', lambda mem, file: file.set(tmpdir='/tmp'))
|
|
37
|
+
|
|
38
|
+
See also
|
|
39
|
+
--------
|
|
40
|
+
|
|
41
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
42
|
+
|
|
43
|
+
Ported from `Mojo::Asset::Memory <https://github.com/mojolicious/mojo/blob/v9.51/lib/Mojo/Asset/Memory.pm>`_
|
|
44
|
+
of Mojolicious 9.51.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
import Pyjo.Asset
|
|
48
|
+
import Pyjo.Asset.File
|
|
49
|
+
import Pyjo.Exception
|
|
50
|
+
import Pyjo.File
|
|
51
|
+
|
|
52
|
+
import builtins
|
|
53
|
+
import os
|
|
54
|
+
import time
|
|
55
|
+
|
|
56
|
+
from typing import Self
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
type Anything = builtins.object
|
|
60
|
+
|
|
61
|
+
# The time the program started, as Perl's $^T: the time this module is loaded
|
|
62
|
+
MTIME = time.time()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Pyjo_Asset_Memory(Pyjo.Asset.object):
|
|
66
|
+
"""
|
|
67
|
+
:mod:`Pyjo.Asset.Memory` inherits all methods from :mod:`Pyjo.Asset`
|
|
68
|
+
and implements the following new ones.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
auto_upgrade: bool | None = None,
|
|
75
|
+
end_range: int | None = None,
|
|
76
|
+
max_memory_size: int | None = None,
|
|
77
|
+
mtime: float | None = None,
|
|
78
|
+
start_range: int = 0,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""::
|
|
81
|
+
|
|
82
|
+
mem = Pyjo.Asset.Memory.new()
|
|
83
|
+
mem = Pyjo.Asset.Memory.new(max_memory_size=1024, auto_upgrade=True)
|
|
84
|
+
|
|
85
|
+
Construct a new :mod:`Pyjo.Asset.Memory` object.
|
|
86
|
+
"""
|
|
87
|
+
super().__init__(end_range=end_range, start_range=start_range)
|
|
88
|
+
|
|
89
|
+
self.auto_upgrade = auto_upgrade
|
|
90
|
+
"""::
|
|
91
|
+
|
|
92
|
+
boolean = mem.auto_upgrade
|
|
93
|
+
mem.auto_upgrade = boolean
|
|
94
|
+
|
|
95
|
+
Try to detect if content size exceeds :attr:`max_memory_size` limit
|
|
96
|
+
and automatically upgrade to a :mod:`Pyjo.Asset.File` object.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
self._max_memory_size: int | None = max_memory_size
|
|
100
|
+
|
|
101
|
+
self._content = b''
|
|
102
|
+
self._frozen = False
|
|
103
|
+
self._mtime = mtime if mtime is not None else MTIME
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def max_memory_size(self) -> int:
|
|
107
|
+
"""::
|
|
108
|
+
|
|
109
|
+
size = mem.max_memory_size
|
|
110
|
+
mem.max_memory_size = 1024
|
|
111
|
+
|
|
112
|
+
Maximum size in bytes of data to keep in memory before automatically
|
|
113
|
+
upgrading to a :mod:`Pyjo.Asset.File` object, defaults to the value
|
|
114
|
+
of the ``PYJO_MAX_MEMORY_SIZE`` environment variable or ``262144``
|
|
115
|
+
(256KiB).
|
|
116
|
+
"""
|
|
117
|
+
if self._max_memory_size is None:
|
|
118
|
+
self._max_memory_size = int(os.environ.get('PYJO_MAX_MEMORY_SIZE') or 262144)
|
|
119
|
+
return self._max_memory_size
|
|
120
|
+
|
|
121
|
+
@max_memory_size.setter
|
|
122
|
+
def max_memory_size(self, value: int) -> None:
|
|
123
|
+
self._max_memory_size = value
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def mtime(self) -> float:
|
|
127
|
+
"""::
|
|
128
|
+
|
|
129
|
+
mtime = mem.mtime
|
|
130
|
+
mem.mtime = 1408567500
|
|
131
|
+
|
|
132
|
+
Modification time of asset, defaults to the time the program started,
|
|
133
|
+
as Perl's ``$^T``.
|
|
134
|
+
"""
|
|
135
|
+
return self._mtime
|
|
136
|
+
|
|
137
|
+
@mtime.setter
|
|
138
|
+
def mtime(self, value: float) -> None:
|
|
139
|
+
self._mtime = value
|
|
140
|
+
|
|
141
|
+
def add_chunk(self, chunk: bytes) -> Pyjo.Asset.object:
|
|
142
|
+
"""::
|
|
143
|
+
|
|
144
|
+
mem = mem.add_chunk(b'foo bar baz')
|
|
145
|
+
file = mem.add_chunk(b'abc' * 262144)
|
|
146
|
+
|
|
147
|
+
Add chunk of data and upgrade to :mod:`Pyjo.Asset.File` object if
|
|
148
|
+
necessary.
|
|
149
|
+
"""
|
|
150
|
+
if self._frozen:
|
|
151
|
+
raise Pyjo.Exception.new('Asset has been upgraded and is now frozen')
|
|
152
|
+
if self.auto_upgrade and (self.size + len(chunk)) > self.max_memory_size:
|
|
153
|
+
file = Pyjo.Asset.File.new()
|
|
154
|
+
self.emit('upgrade', file)
|
|
155
|
+
self._frozen = True
|
|
156
|
+
return file.add_chunk(self.slurp() + chunk)
|
|
157
|
+
|
|
158
|
+
self._content += chunk
|
|
159
|
+
return self
|
|
160
|
+
|
|
161
|
+
def contains(self, string: bytes) -> int:
|
|
162
|
+
"""::
|
|
163
|
+
|
|
164
|
+
position = mem.contains(b'bar')
|
|
165
|
+
|
|
166
|
+
Check if asset contains a specific string.
|
|
167
|
+
"""
|
|
168
|
+
start = self.start_range
|
|
169
|
+
pos = self._content.find(string, start)
|
|
170
|
+
if start and pos >= 0:
|
|
171
|
+
pos -= start
|
|
172
|
+
end = self.end_range
|
|
173
|
+
|
|
174
|
+
return -1 if end and (pos + len(string)) >= end else pos
|
|
175
|
+
|
|
176
|
+
def get_chunk(self, offset: int, max: int | None = None) -> bytes:
|
|
177
|
+
"""::
|
|
178
|
+
|
|
179
|
+
chunk = mem.get_chunk(offset)
|
|
180
|
+
chunk = mem.get_chunk(offset, max)
|
|
181
|
+
|
|
182
|
+
Get chunk of data starting from a specific position, defaults to a
|
|
183
|
+
maximum chunk size of ``131072`` bytes (128KiB).
|
|
184
|
+
"""
|
|
185
|
+
if max is None:
|
|
186
|
+
max = 131072
|
|
187
|
+
|
|
188
|
+
offset += self.start_range
|
|
189
|
+
if (end := self.end_range) is not None and (offset + max) > end:
|
|
190
|
+
max = end + 1 - offset
|
|
191
|
+
|
|
192
|
+
return self._content[offset : offset + max] if max > 0 else b''
|
|
193
|
+
|
|
194
|
+
def move_to(self, to: Anything) -> Self:
|
|
195
|
+
"""::
|
|
196
|
+
|
|
197
|
+
mem = mem.move_to('/home/sri/foo.txt')
|
|
198
|
+
|
|
199
|
+
Move asset data into a specific file.
|
|
200
|
+
"""
|
|
201
|
+
Pyjo.File.new(to).spew(self._content)
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def size(self) -> int:
|
|
206
|
+
"""::
|
|
207
|
+
|
|
208
|
+
size = mem.size
|
|
209
|
+
|
|
210
|
+
Size of asset data in bytes.
|
|
211
|
+
"""
|
|
212
|
+
return len(self._content)
|
|
213
|
+
|
|
214
|
+
def slurp(self) -> bytes:
|
|
215
|
+
"""::
|
|
216
|
+
|
|
217
|
+
content = mem.slurp()
|
|
218
|
+
|
|
219
|
+
Read all asset data at once.
|
|
220
|
+
"""
|
|
221
|
+
return self._content
|
|
222
|
+
|
|
223
|
+
def to_file(self) -> Pyjo.Asset.File.object:
|
|
224
|
+
"""::
|
|
225
|
+
|
|
226
|
+
file = mem.to_file()
|
|
227
|
+
|
|
228
|
+
Convert asset to :mod:`Pyjo.Asset.File` object.
|
|
229
|
+
"""
|
|
230
|
+
return Pyjo.Asset.File.new().add_chunk(self.slurp())
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
new = Pyjo_Asset_Memory.new
|
|
234
|
+
object = Pyjo_Asset_Memory
|
|
235
|
+
Memory = Pyjo_Asset_Memory
|