Pyjoyment 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/Asset/__init__.py ADDED
@@ -0,0 +1,200 @@
1
+ """
2
+ Pyjo.Asset - HTTP content storage base class
3
+ ============================================
4
+ ::
5
+
6
+ from Pyjo.Asset import Asset
7
+
8
+ class MyAsset(Asset):
9
+ def add_chunk(self, chunk): ...
10
+ def contains(self, string): ...
11
+ def get_chunk(self, offset, max=None): ...
12
+ def move_to(self, to): ...
13
+ @property
14
+ def mtime(self): ...
15
+ @property
16
+ def size(self): ...
17
+ def slurp(self): ...
18
+ def to_file(self): ...
19
+
20
+ # Or import the module, as in Google Python Style Guide
21
+
22
+ import Pyjo.Asset
23
+
24
+ class MyAsset(Pyjo.Asset.object):
25
+ def add_chunk(self, chunk): ...
26
+ def contains(self, string): ...
27
+ def get_chunk(self, offset, max=None): ...
28
+ def move_to(self, to): ...
29
+ @property
30
+ def mtime(self): ...
31
+ @property
32
+ def size(self): ...
33
+ def slurp(self): ...
34
+ def to_file(self): ...
35
+
36
+ :mod:`Pyjo.Asset` is an abstract base class for HTTP content storage
37
+ backends, like :mod:`Pyjo.Asset.File` and :mod:`Pyjo.Asset.Memory`.
38
+
39
+ Events
40
+ ------
41
+
42
+ :mod:`Pyjo.Asset` inherits all events from :mod:`Pyjo.EventEmitter`.
43
+
44
+ See also
45
+ --------
46
+
47
+ :mod:`Pyjoyment`, https://mojolicious.org.
48
+
49
+ Ported from `Mojo::Asset <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Asset.pm>`_
50
+ of Mojolicious 9.49.
51
+ """
52
+
53
+ import Pyjo.EventEmitter
54
+
55
+ import builtins
56
+
57
+ from typing import Self
58
+
59
+
60
+ type Anything = builtins.object
61
+
62
+
63
+ class Pyjo_Asset(Pyjo.EventEmitter.object):
64
+ """
65
+ :mod:`Pyjo.Asset` inherits all methods from :mod:`Pyjo.EventEmitter` and
66
+ implements the following new ones.
67
+ """
68
+
69
+ def __init__(self, *, end_range: int | None = None, start_range: int = 0) -> None:
70
+ """::
71
+
72
+ asset = Pyjo.Asset.new()
73
+ asset = Pyjo.Asset.new(start_range=3, end_range=8)
74
+
75
+ Construct a new :mod:`Pyjo.Asset` object.
76
+ """
77
+ super().__init__()
78
+
79
+ self.end_range = end_range
80
+ """::
81
+
82
+ end = asset.end_range
83
+ asset.end_range = 8
84
+
85
+ Pretend file ends earlier.
86
+ """
87
+
88
+ self.start_range = start_range
89
+ """::
90
+
91
+ start = asset.start_range
92
+ asset.start_range = 3
93
+
94
+ Pretend file starts later.
95
+ """
96
+
97
+ def add_chunk(self, chunk: bytes) -> 'Pyjo_Asset':
98
+ """::
99
+
100
+ asset = asset.add_chunk(b'foo bar baz')
101
+
102
+ Add chunk of data to asset. Meant to be overloaded in a subclass.
103
+ """
104
+ raise NotImplementedError('Method "add_chunk" not implemented by subclass')
105
+
106
+ def contains(self, string: bytes) -> int:
107
+ """::
108
+
109
+ position = asset.contains(b'bar')
110
+
111
+ Check if asset contains a specific string. Meant to be overloaded in a
112
+ subclass.
113
+ """
114
+ raise NotImplementedError('Method "contains" not implemented by subclass')
115
+
116
+ def get_chunk(self, offset: int, max: int | None = None) -> bytes:
117
+ """::
118
+
119
+ chunk = asset.get_chunk(offset)
120
+ chunk = asset.get_chunk(offset, max)
121
+
122
+ Get chunk of data starting from a specific position, defaults to a
123
+ maximum chunk size of ``131072`` bytes (128KiB). Meant to be overloaded
124
+ in a subclass.
125
+ """
126
+ raise NotImplementedError('Method "get_chunk" not implemented by subclass')
127
+
128
+ @property
129
+ def is_file(self) -> bool:
130
+ """::
131
+
132
+ boolean = asset.is_file
133
+
134
+ False, this is not a :mod:`Pyjo.Asset.File` object.
135
+ """
136
+ return False
137
+
138
+ @property
139
+ def is_range(self) -> bool:
140
+ """::
141
+
142
+ boolean = asset.is_range
143
+
144
+ Check if asset has a :attr:`start_range` or :attr:`end_range`.
145
+ """
146
+ return bool(self.end_range or self.start_range)
147
+
148
+ def move_to(self, to: Anything) -> Self:
149
+ """::
150
+
151
+ asset = asset.move_to('/home/sri/foo.txt')
152
+
153
+ Move asset data into a specific file. Meant to be overloaded in a
154
+ subclass.
155
+ """
156
+ raise NotImplementedError('Method "move_to" not implemented by subclass')
157
+
158
+ @property
159
+ def mtime(self) -> float:
160
+ """::
161
+
162
+ mtime = asset.mtime
163
+
164
+ Modification time of asset. Meant to be overloaded in a subclass.
165
+ """
166
+ raise NotImplementedError('Method "mtime" not implemented by subclass')
167
+
168
+ @property
169
+ def size(self) -> int:
170
+ """::
171
+
172
+ size = asset.size
173
+
174
+ Size of asset data in bytes. Meant to be overloaded in a subclass.
175
+ """
176
+ raise NotImplementedError('Method "size" not implemented by subclass')
177
+
178
+ def slurp(self) -> bytes:
179
+ """::
180
+
181
+ content = asset.slurp()
182
+
183
+ Read all asset data at once. Meant to be overloaded in a subclass.
184
+ """
185
+ raise NotImplementedError('Method "slurp" not implemented by subclass')
186
+
187
+ def to_file(self) -> 'Pyjo_Asset':
188
+ """::
189
+
190
+ file = asset.to_file()
191
+
192
+ Convert asset to :mod:`Pyjo.Asset.File` object. Meant to be overloaded
193
+ in a subclass.
194
+ """
195
+ raise NotImplementedError('Method "to_file" not implemented by subclass')
196
+
197
+
198
+ new = Pyjo_Asset.new
199
+ object = Pyjo_Asset
200
+ Asset = Pyjo_Asset
Pyjo/Base.py ADDED
@@ -0,0 +1,215 @@
1
+ r"""
2
+ Pyjo.Base - Minimal base class for Pyjo projects
3
+ ================================================
4
+ ::
5
+
6
+ from Pyjo.Base import Base
7
+
8
+ from dataclasses import dataclass
9
+
10
+ @dataclass
11
+ class Cat(Base):
12
+ name: str = 'Nyan'
13
+ age: int = 4
14
+
15
+ mew = Cat(name='Longcat')
16
+ print(mew.age)
17
+
18
+ # Or import the module, as in Google Python Style Guide
19
+
20
+ import Pyjo.Base
21
+
22
+ from dataclasses import dataclass, field
23
+
24
+ @dataclass
25
+ class Cat(Pyjo.Base.object):
26
+ name: str = 'Nyan'
27
+ age: int = 4
28
+ weight: int = 4
29
+
30
+ @dataclass
31
+ class Tiger(Cat):
32
+ friend: Cat = field(default_factory=Cat)
33
+ stripes: int = 42
34
+
35
+ mew = Cat.new(name='Longcat')
36
+ print(mew.age)
37
+ print(mew.set(age=3, weight=5).age)
38
+
39
+ rawr = Tiger.new(stripes=38, weight=250)
40
+ print(rawr.tap(lambda rawr: rawr.friend.set(name='Tacgnol')).weight)
41
+
42
+ :mod:`Pyjo.Base` is a simple base class for :mod:`Pyjo` projects with fluent
43
+ interfaces.
44
+
45
+ The attributes of an object are the attributes of Python. A dataclass, as
46
+ above, or ``__init__`` gives them their default values, and a property builds
47
+ a default value the first time it is read, as a callback of ``has`` does in
48
+ Mojolicious. ::
49
+
50
+ class Lion(Cat):
51
+ @property
52
+ def pride(self):
53
+ if not hasattr(self, '_pride'):
54
+ self._pride = [Cat.new(), Cat.new()]
55
+ return self._pride
56
+
57
+ Constructors
58
+ ------------
59
+
60
+ The constructor of a class and :meth:`~Pyjo_Base.new` make the same object, and
61
+ both are correct. :meth:`~Pyjo_Base.new` is the constructor of Mojolicious,
62
+ which the documentation uses, after ``import`` of the module. Each module has
63
+ its class under the name of the module too, as ``Path`` of :mod:`Pyjo.Path`,
64
+ for the constructor of Python, after ``from ... import`` of the class. ::
65
+
66
+ import Pyjo.Path
67
+
68
+ path = Pyjo.Path.new('/foo/bar')
69
+
70
+ # Or import the class
71
+
72
+ from Pyjo.Path import Path
73
+
74
+ path = Path('/foo/bar')
75
+
76
+ The synopsis of each module shows its first example in both forms.
77
+
78
+ Fluent interfaces
79
+ -----------------
80
+
81
+ Fluent interfaces are a way to design object-oriented APIs around method
82
+ chaining to create domain-specific languages, with the goal of making the
83
+ readability of the source code close to written prose. ::
84
+
85
+ @dataclass
86
+ class Duck(Pyjo.Base.object):
87
+ name: str | None = None
88
+
89
+ def quack(self):
90
+ print(f'{self.name}: Quack!')
91
+
92
+ :mod:`Pyjo.Base` will help you with this by having :meth:`~Pyjo_Base.set`
93
+ return its invocant (``self``) when it is used to assign new attribute values. ::
94
+
95
+ Duck.new().set(name='Donald').quack()
96
+
97
+ In this case :meth:`~Pyjo_Base.set` is called on the object created by
98
+ ``Duck.new()``. It assigns a new attribute value and then returns the ``Duck``
99
+ object, so the ``quack`` method can be called on it afterwards. These method
100
+ chains can continue until one of the methods called does not return the
101
+ ``Duck`` object.
102
+
103
+ See also
104
+ --------
105
+
106
+ :mod:`Pyjoyment`, https://mojolicious.org.
107
+
108
+ Ported from `Mojo::Base <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Base.pm>`_
109
+ of Mojolicious 9.49.
110
+
111
+ Classes
112
+ -------
113
+ """
114
+
115
+ from collections.abc import Callable
116
+ from typing import Any, Self
117
+
118
+
119
+ # Left out from Mojolicious: the flags of import, -strict, -base, -role, -signatures and -async_await, as Python has
120
+ # them on its own, and has, as the attributes of Python take its place
121
+
122
+
123
+ class Pyjo_Base:
124
+ """
125
+ :mod:`Pyjo.Base` implements the following methods.
126
+ """
127
+
128
+ # Not in Mojolicious: no __dict__ of its own, so that a subclass with __slots__ has none either
129
+ __slots__ = ()
130
+
131
+ # Left out from Mojolicious: attr, as the attributes of Python, the properties and weakref.proxy take its place
132
+
133
+ @classmethod
134
+ def new(cls, *args: Any, **kwargs: Any) -> Self:
135
+ """::
136
+
137
+ obj = SubClass.new()
138
+ obj = SubClass.new(name='value')
139
+ obj = SubClass.new(**{'name': 'value'})
140
+
141
+ This base class provides a basic constructor, which passes its
142
+ arguments to the constructor of the class: the attribute values are
143
+ keyword arguments.
144
+ """
145
+ return cls(*args, **kwargs)
146
+
147
+ # Not in Mojolicious: the expression form of an assignment, as the accessors of Mojo chain when they set a value
148
+ def set(self, **kwargs: Any) -> Self:
149
+ """::
150
+
151
+ obj = obj.set(name='value')
152
+ obj = obj.set(name='value', other='value')
153
+
154
+ Assign attribute values and return the object, so that a chain of
155
+ method calls can continue. ::
156
+
157
+ # Longer version
158
+ obj.name = 'value'
159
+ """
160
+ for k, v in sorted(kwargs.items()):
161
+ setattr(self, k, v)
162
+ return self
163
+
164
+ def tap(self, cb: Callable[..., Any] | str, *args: Any, **kwargs: Any) -> Self:
165
+ """::
166
+
167
+ obj = obj.tap(lambda obj: ...)
168
+ obj = obj.tap('some_method')
169
+ obj = obj.tap('some_method', *args, **kwargs)
170
+
171
+ Tap into a method chain to perform operations on an object within the
172
+ chain (also known as a K combinator or Kestrel). The object will be
173
+ the first argument passed to the callback. The callback's return value
174
+ will be ignored; instead, the object (the callback's first argument)
175
+ will be the return value. In this way, arbitrary code can be used
176
+ within (i.e., spliced or tapped into) a chained set of object method
177
+ calls. A name of an attribute that is not a method is read, which
178
+ builds the default value of a property. ::
179
+
180
+ # Longer version
181
+ obj = obj.tap(lambda obj: obj.some_method(*args))
182
+
183
+ # Inject side effects into a method chain
184
+ obj.set(foo='A').tap(lambda obj: print(obj.foo)).set(foo='B')
185
+ """
186
+ tap(self, cb, *args, **kwargs)
187
+ return self
188
+
189
+ # Left out from Mojolicious: with_roles, as Pyjoyment has no roles
190
+
191
+
192
+ # Not in Mojolicious: a function of its own, for the classes that are not Pyjo.Base, as a list and a string
193
+ def tap(obj: Any, cb: Callable[..., Any] | str, *args: Any, **kwargs: Any) -> None:
194
+ """::
195
+
196
+ tap(obj, lambda obj: ...)
197
+ tap(obj, 'some_method', *args, **kwargs)
198
+
199
+ Call the callback with the object, or its method of the name with the
200
+ arguments, or read its attribute of the name, as :meth:`Pyjo_Base.tap` and
201
+ the ``tap`` of the classes that are not :mod:`Pyjo.Base` do.
202
+ """
203
+ if callable(cb):
204
+ cb(obj, *args, **kwargs)
205
+ return
206
+ value = getattr(obj, cb)
207
+ if callable(value):
208
+ value(*args, **kwargs)
209
+ elif args or kwargs:
210
+ raise TypeError(f'{cb!r} of {type(obj).__name__} is not callable')
211
+
212
+
213
+ new = Pyjo_Base.new
214
+ object = Pyjo_Base
215
+ Base = Pyjo_Base
Pyjo/Cache.py ADDED
@@ -0,0 +1,119 @@
1
+ """
2
+ Pyjo.Cache - Naive in-memory cache
3
+ ==================================
4
+ ::
5
+
6
+ from Pyjo.Cache import Cache
7
+
8
+ cache = Cache(max_keys=50)
9
+ cache.set('foo', 'bar')
10
+ foo = cache.get('foo')
11
+
12
+ # Or import the module, as in Google Python Style Guide
13
+
14
+ import Pyjo.Cache
15
+
16
+ cache = Pyjo.Cache.new(max_keys=50)
17
+ cache.set('foo', 'bar')
18
+ foo = cache.get('foo')
19
+
20
+ :mod:`Pyjo.Cache` is a naive in-memory cache with size limits.
21
+
22
+ See also
23
+ --------
24
+
25
+ :mod:`Pyjoyment`, https://mojolicious.org.
26
+
27
+ Ported from `Mojo::Cache <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Cache.pm>`_
28
+ of Mojolicious 9.49.
29
+ """
30
+
31
+ import Pyjo.Base
32
+
33
+ import builtins
34
+
35
+ from collections.abc import Hashable
36
+ from typing import Self
37
+
38
+
39
+ type Anything = builtins.object
40
+
41
+
42
+ class Pyjo_Cache[K: Hashable, V](Pyjo.Base.object):
43
+ """
44
+ :mod:`Pyjo.Cache` inherits all methods from :mod:`Pyjo.Base` and
45
+ implements the following new ones.
46
+ """
47
+
48
+ def __init__(self, max_keys: int = 100) -> None:
49
+ """::
50
+
51
+ cache = Pyjo.Cache.new()
52
+ cache = Pyjo.Cache.new(max_keys=50)
53
+
54
+ Construct a new :mod:`Pyjo.Cache` object.
55
+ """
56
+ self.max_keys = max_keys
57
+ """::
58
+
59
+ max = cache.max_keys
60
+ cache.max_keys = 50
61
+
62
+ Maximum number of cache keys, defaults to ``100``. Setting the value to
63
+ ``0`` will disable caching.
64
+ """
65
+
66
+ # The order of the keys of a dict is the queue of Mojo
67
+ self._cache: dict[K, V] = {}
68
+
69
+ # Not in Mojolicious: the protocol of a container
70
+ def __contains__(self, key: Anything) -> bool:
71
+ """::
72
+
73
+ boolean = 'foo' in cache
74
+
75
+ Check if a value is cached.
76
+ """
77
+ return key in self._cache
78
+
79
+ # Not in Mojolicious: the protocol of a container
80
+ def __len__(self) -> int:
81
+ """::
82
+
83
+ count = len(cache)
84
+
85
+ Number of cached values.
86
+ """
87
+ return len(self._cache)
88
+
89
+ def get(self, key: K) -> V | None:
90
+ """::
91
+
92
+ value = cache.get('foo')
93
+
94
+ Get cached value.
95
+ """
96
+ return self._cache.get(key)
97
+
98
+ # The set() of Pyjo.Base sets attributes, the set() of Mojo caches a value
99
+ def set(self, key: K, value: V) -> Self: # pyright: ignore[reportIncompatibleMethodOverride]
100
+ """::
101
+
102
+ cache = cache.set('foo', 'bar')
103
+
104
+ Set cached value.
105
+ """
106
+ if (max := self.max_keys) <= 0:
107
+ return self
108
+
109
+ cache = self._cache
110
+ while len(cache) >= max:
111
+ del cache[next(iter(cache))]
112
+ cache[key] = value
113
+
114
+ return self
115
+
116
+
117
+ new = Pyjo_Cache.new
118
+ object = Pyjo_Cache
119
+ Cache = Pyjo_Cache