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/URL/__init__.py
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.URL - Uniform Resource Locator
|
|
3
|
+
===================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.URL import URL
|
|
7
|
+
|
|
8
|
+
# Parse
|
|
9
|
+
url = URL('http://sri:foo@example.com:3000/foo?foo=bar#23')
|
|
10
|
+
print(url.scheme)
|
|
11
|
+
print(url.userinfo)
|
|
12
|
+
print(url.host)
|
|
13
|
+
print(url.port)
|
|
14
|
+
print(url.path)
|
|
15
|
+
print(url.query)
|
|
16
|
+
print(url.fragment)
|
|
17
|
+
|
|
18
|
+
# Or import the module, as in Google Python Style Guide
|
|
19
|
+
|
|
20
|
+
import Pyjo.URL
|
|
21
|
+
|
|
22
|
+
# Parse
|
|
23
|
+
url = Pyjo.URL.new('http://sri:foo@example.com:3000/foo?foo=bar#23')
|
|
24
|
+
print(url.scheme)
|
|
25
|
+
print(url.userinfo)
|
|
26
|
+
print(url.host)
|
|
27
|
+
print(url.port)
|
|
28
|
+
print(url.path)
|
|
29
|
+
print(url.query)
|
|
30
|
+
print(url.fragment)
|
|
31
|
+
|
|
32
|
+
# Build
|
|
33
|
+
url = Pyjo.URL.new()
|
|
34
|
+
url.scheme = 'http'
|
|
35
|
+
url.host = 'example.com'
|
|
36
|
+
url.port = 3000
|
|
37
|
+
url.path = '/foo/bar'
|
|
38
|
+
url.query = ('foo', 'bar')
|
|
39
|
+
url.fragment = '23'
|
|
40
|
+
print(url)
|
|
41
|
+
|
|
42
|
+
:mod:`Pyjo.URL` implements a subset of :rfc:`3986`, :rfc:`3987` and the
|
|
43
|
+
`URL Living Standard <https://url.spec.whatwg.org>`_ for Uniform Resource
|
|
44
|
+
Locators with support for IDNA and IRIs.
|
|
45
|
+
|
|
46
|
+
See also
|
|
47
|
+
--------
|
|
48
|
+
|
|
49
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
50
|
+
|
|
51
|
+
Ported from `Mojo::URL <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/URL.pm>`_
|
|
52
|
+
of Mojolicious 9.49.
|
|
53
|
+
|
|
54
|
+
Classes
|
|
55
|
+
-------
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import Pyjo.Base
|
|
59
|
+
import Pyjo.Parameters
|
|
60
|
+
import Pyjo.Path
|
|
61
|
+
import Pyjo.String.Mixin
|
|
62
|
+
|
|
63
|
+
from Pyjo.Util import decode, punycode_decode, punycode_encode, url_escape, url_unescape
|
|
64
|
+
|
|
65
|
+
import builtins
|
|
66
|
+
import re
|
|
67
|
+
|
|
68
|
+
from typing import Self, cast
|
|
69
|
+
from urllib.parse import ParseResult, ParseResultBytes, SplitResult, SplitResultBytes, urlsplit
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# The module ends with `object = Pyjo_URL`, which an annotation of `object`
|
|
73
|
+
# would mean, as annotations are evaluated lazily
|
|
74
|
+
type Anything = builtins.object
|
|
75
|
+
|
|
76
|
+
# A result of urllib.parse.urlsplit or urllib.parse.urlparse
|
|
77
|
+
type UrllibResult = SplitResult | ParseResult | SplitResultBytes | ParseResultBytes
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Pyjo_URL(Pyjo.Base.object, Pyjo.String.Mixin.object):
|
|
81
|
+
"""
|
|
82
|
+
:mod:`Pyjo.URL` inherits all methods from :mod:`Pyjo.Base` and
|
|
83
|
+
:mod:`Pyjo.String.Mixin` and implements the following new ones.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(self, url: 'str | bytes | UrllibResult | None' = None) -> None:
|
|
87
|
+
"""::
|
|
88
|
+
|
|
89
|
+
url = Pyjo.URL.new()
|
|
90
|
+
url = Pyjo.URL.new('http://127.0.0.1:3000/foo?f=b&baz=2#foo')
|
|
91
|
+
url = Pyjo.URL.new(urlsplit('http://127.0.0.1:3000/foo?f=b&baz=2#foo'))
|
|
92
|
+
|
|
93
|
+
Construct a new :mod:`Pyjo.URL` object and :meth:`parse` URL if necessary.
|
|
94
|
+
"""
|
|
95
|
+
self._base: Pyjo_URL | None = None
|
|
96
|
+
self._path: Pyjo.Path.object | None = None
|
|
97
|
+
self._query: Pyjo.Parameters.object | None = None
|
|
98
|
+
|
|
99
|
+
self.fragment: str | None = None
|
|
100
|
+
"""::
|
|
101
|
+
|
|
102
|
+
fragment = url.fragment
|
|
103
|
+
url.fragment = '♥pyjoyment♥'
|
|
104
|
+
|
|
105
|
+
Fragment part of this URL. ::
|
|
106
|
+
|
|
107
|
+
# "yada"
|
|
108
|
+
Pyjo.URL.new('http://example.com/foo?bar=baz#yada').fragment
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
self.host: str | None = None
|
|
112
|
+
"""::
|
|
113
|
+
|
|
114
|
+
host = url.host
|
|
115
|
+
url.host = '127.0.0.1'
|
|
116
|
+
|
|
117
|
+
Host part of this URL. ::
|
|
118
|
+
|
|
119
|
+
# "example.com"
|
|
120
|
+
Pyjo.URL.new('http://sri:t3st@example.com:8080/foo').host
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
self.port: int | None = None
|
|
124
|
+
"""::
|
|
125
|
+
|
|
126
|
+
port = url.port
|
|
127
|
+
url.port = 8080
|
|
128
|
+
|
|
129
|
+
Port part of this URL. ::
|
|
130
|
+
|
|
131
|
+
# 8080
|
|
132
|
+
Pyjo.URL.new('http://sri:t3st@example.com:8080/foo').port
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
self.scheme: str | None = None
|
|
136
|
+
"""::
|
|
137
|
+
|
|
138
|
+
scheme = url.scheme
|
|
139
|
+
url.scheme = 'http'
|
|
140
|
+
|
|
141
|
+
Scheme part of this URL. ::
|
|
142
|
+
|
|
143
|
+
# "http"
|
|
144
|
+
Pyjo.URL.new('http://example.com/foo').scheme
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
self.userinfo: str | None = None
|
|
148
|
+
"""::
|
|
149
|
+
|
|
150
|
+
info = url.userinfo
|
|
151
|
+
url.userinfo = 'root:♥'
|
|
152
|
+
|
|
153
|
+
Userinfo part of this URL. ::
|
|
154
|
+
|
|
155
|
+
# "sri:t3st"
|
|
156
|
+
Pyjo.URL.new('https://sri:t3st@example.com/foo').userinfo
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
if url is not None:
|
|
160
|
+
self.parse(url)
|
|
161
|
+
|
|
162
|
+
def __bool__(self) -> bool:
|
|
163
|
+
"""::
|
|
164
|
+
|
|
165
|
+
boolean = bool(url)
|
|
166
|
+
|
|
167
|
+
Always true.
|
|
168
|
+
"""
|
|
169
|
+
return True
|
|
170
|
+
|
|
171
|
+
def __str__(self) -> str:
|
|
172
|
+
"""::
|
|
173
|
+
|
|
174
|
+
string = str(url)
|
|
175
|
+
|
|
176
|
+
Alias for :meth:`to_str`.
|
|
177
|
+
"""
|
|
178
|
+
return self.to_str()
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def base(self) -> 'Pyjo_URL':
|
|
182
|
+
"""::
|
|
183
|
+
|
|
184
|
+
base = url.base
|
|
185
|
+
url.base = Pyjo.URL.new()
|
|
186
|
+
|
|
187
|
+
Base of this URL, defaults to a :mod:`Pyjo.URL` object. ::
|
|
188
|
+
|
|
189
|
+
# "http://example.com/a/b?c"
|
|
190
|
+
Pyjo.URL.new('/a/b?c').set(base=Pyjo.URL.new('http://example.com')).to_abs()
|
|
191
|
+
"""
|
|
192
|
+
if self._base is None:
|
|
193
|
+
self._base = Pyjo_URL()
|
|
194
|
+
return self._base
|
|
195
|
+
|
|
196
|
+
@base.setter
|
|
197
|
+
def base(self, value: 'Pyjo_URL') -> None:
|
|
198
|
+
self._base = value
|
|
199
|
+
|
|
200
|
+
def clone(self) -> Self:
|
|
201
|
+
"""::
|
|
202
|
+
|
|
203
|
+
url2 = url.clone()
|
|
204
|
+
|
|
205
|
+
Return a new :mod:`Pyjo.URL` object cloned from this URL.
|
|
206
|
+
"""
|
|
207
|
+
clone = self.new()
|
|
208
|
+
clone.fragment, clone.host, clone.port = self.fragment, self.host, self.port
|
|
209
|
+
clone.scheme, clone.userinfo = self.scheme, self.userinfo
|
|
210
|
+
clone._base = self._base.clone() if self._base is not None else None
|
|
211
|
+
clone._path = self._path.clone() if self._path is not None else None
|
|
212
|
+
clone._query = self._query.clone() if self._query is not None else None
|
|
213
|
+
return clone
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def host_port(self) -> str | None:
|
|
217
|
+
"""::
|
|
218
|
+
|
|
219
|
+
host_port = url.host_port
|
|
220
|
+
url.host_port = 'example.com:8080'
|
|
221
|
+
|
|
222
|
+
Normalized version of :attr:`host` and :attr:`port`. ::
|
|
223
|
+
|
|
224
|
+
# "xn--n3h.net:8080"
|
|
225
|
+
Pyjo.URL.new('http://☃.net:8080/test').host_port
|
|
226
|
+
|
|
227
|
+
# "example.com"
|
|
228
|
+
Pyjo.URL.new('http://example.com/test').host_port
|
|
229
|
+
"""
|
|
230
|
+
if (host := self.ihost) is None:
|
|
231
|
+
return None
|
|
232
|
+
if (port := self.port) is None:
|
|
233
|
+
return host
|
|
234
|
+
return f'{host}:{port}'
|
|
235
|
+
|
|
236
|
+
@host_port.setter
|
|
237
|
+
def host_port(self, value: str | None) -> None:
|
|
238
|
+
if value is None:
|
|
239
|
+
return
|
|
240
|
+
if m := re.search(r':(\d+)$', value):
|
|
241
|
+
self.port = int(m[1])
|
|
242
|
+
value = value[: m.start()]
|
|
243
|
+
host = url_unescape(value)
|
|
244
|
+
if re.search(r'[^\x00-\x7f]', host):
|
|
245
|
+
self.ihost = host
|
|
246
|
+
else:
|
|
247
|
+
self.host = host
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def ihost(self) -> str | None:
|
|
251
|
+
"""::
|
|
252
|
+
|
|
253
|
+
ihost = url.ihost
|
|
254
|
+
url.ihost = 'xn--bcher-kva.ch'
|
|
255
|
+
|
|
256
|
+
Host part of this URL in punycode format. ::
|
|
257
|
+
|
|
258
|
+
# "xn--n3h.net"
|
|
259
|
+
Pyjo.URL.new('http://☃.net').ihost
|
|
260
|
+
|
|
261
|
+
# "example.com"
|
|
262
|
+
Pyjo.URL.new('http://example.com').ihost
|
|
263
|
+
"""
|
|
264
|
+
# Check if host needs to be encoded
|
|
265
|
+
if (host := self.host) is None:
|
|
266
|
+
return None
|
|
267
|
+
if not re.search(r'[^\x00-\x7f]', host):
|
|
268
|
+
return host
|
|
269
|
+
|
|
270
|
+
# Encode
|
|
271
|
+
return '.'.join(
|
|
272
|
+
f'xn--{punycode_encode(label)}' if re.search(r'[^\x00-\x7f]', label) else label for label in host.split('.')
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
@ihost.setter
|
|
276
|
+
def ihost(self, value: str) -> None:
|
|
277
|
+
# Decode
|
|
278
|
+
self.host = '.'.join(
|
|
279
|
+
punycode_decode(m[1]) if (m := re.match(r'^xn--(.+)$', label)) else label for label in value.split('.')
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def is_abs(self) -> bool:
|
|
284
|
+
"""::
|
|
285
|
+
|
|
286
|
+
boolean = url.is_abs
|
|
287
|
+
|
|
288
|
+
Check if URL is absolute. ::
|
|
289
|
+
|
|
290
|
+
# True
|
|
291
|
+
Pyjo.URL.new('http://example.com').is_abs
|
|
292
|
+
Pyjo.URL.new('http://example.com/test/index.html').is_abs
|
|
293
|
+
|
|
294
|
+
# False
|
|
295
|
+
Pyjo.URL.new('test/index.html').is_abs
|
|
296
|
+
Pyjo.URL.new('/test/index.html').is_abs
|
|
297
|
+
Pyjo.URL.new('//example.com/test/index.html').is_abs
|
|
298
|
+
"""
|
|
299
|
+
return bool(self.scheme)
|
|
300
|
+
|
|
301
|
+
def parse(self, url: 'str | bytes | UrllibResult') -> Self:
|
|
302
|
+
"""::
|
|
303
|
+
|
|
304
|
+
url = url.parse('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo')
|
|
305
|
+
url = url.parse(urlsplit('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo'))
|
|
306
|
+
|
|
307
|
+
Parse relative or absolute URL. Bytes are characters 0-255, as a Perl
|
|
308
|
+
byte string, and a result of :func:`urllib.parse.urlsplit` or
|
|
309
|
+
:func:`urllib.parse.urlparse` is taken as the URL it holds. ::
|
|
310
|
+
|
|
311
|
+
# "/test/123"
|
|
312
|
+
Pyjo.URL.new().parse('/test/123?foo=bar').path
|
|
313
|
+
|
|
314
|
+
# "example.com"
|
|
315
|
+
Pyjo.URL.new().parse('http://example.com/test/123?foo=bar').host
|
|
316
|
+
|
|
317
|
+
# "sri@example.com"
|
|
318
|
+
Pyjo.URL.new().parse('mailto:sri@example.com').path
|
|
319
|
+
"""
|
|
320
|
+
# Not in Mojolicious: urllib of the standard library
|
|
321
|
+
if isinstance(url, (SplitResult, ParseResult, SplitResultBytes, ParseResultBytes)):
|
|
322
|
+
url = url.geturl()
|
|
323
|
+
|
|
324
|
+
# Not in Mojolicious: bytes, which are characters 0-255, as a Perl byte string
|
|
325
|
+
if isinstance(url, bytes):
|
|
326
|
+
url = url.decode('latin-1')
|
|
327
|
+
|
|
328
|
+
# Official regex from RFC 3986
|
|
329
|
+
m = re.match(r'^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?', url)
|
|
330
|
+
assert m is not None
|
|
331
|
+
if m[2] is not None:
|
|
332
|
+
self.scheme = m[2]
|
|
333
|
+
if m[5] is not None:
|
|
334
|
+
self.path = m[5]
|
|
335
|
+
if m[7] is not None:
|
|
336
|
+
self.query = m[7]
|
|
337
|
+
if m[9] is not None:
|
|
338
|
+
self.fragment = _decode(url_unescape(m[9]))
|
|
339
|
+
if (auth := m[4]) is not None:
|
|
340
|
+
if info := re.match(r'^([^@]+)@', auth):
|
|
341
|
+
self.userinfo = _decode(url_unescape(info[1]))
|
|
342
|
+
auth = auth[info.end() :]
|
|
343
|
+
self.host_port = auth
|
|
344
|
+
|
|
345
|
+
return self
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def password(self) -> str | None:
|
|
349
|
+
"""::
|
|
350
|
+
|
|
351
|
+
password = url.password
|
|
352
|
+
|
|
353
|
+
Password part of :attr:`userinfo`. ::
|
|
354
|
+
|
|
355
|
+
# "s3cret"
|
|
356
|
+
Pyjo.URL.new('http://isabel:s3cret@pyjoyment.org').password
|
|
357
|
+
|
|
358
|
+
# "s:3:c:r:e:t"
|
|
359
|
+
Pyjo.URL.new('http://isabel:s:3:c:r:e:t@pyjoyment.org').password
|
|
360
|
+
"""
|
|
361
|
+
m = re.search(r':(.*)$', self.userinfo or '')
|
|
362
|
+
return m[1] if m else None
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def path(self) -> Pyjo.Path.object:
|
|
366
|
+
"""::
|
|
367
|
+
|
|
368
|
+
path = url.path
|
|
369
|
+
url.path = 'foo/bar'
|
|
370
|
+
url.path = '/foo/bar'
|
|
371
|
+
url.path = Pyjo.Path.new()
|
|
372
|
+
|
|
373
|
+
Path part of this URL, relative paths will be merged with
|
|
374
|
+
:meth:`Pyjo.Path.Pyjo_Path.merge`, defaults to a :mod:`Pyjo.Path` object. ::
|
|
375
|
+
|
|
376
|
+
# "test"
|
|
377
|
+
Pyjo.URL.new('http://example.com/test/Pyjo').path.parts[0]
|
|
378
|
+
|
|
379
|
+
# "/test/DOM/HTML"
|
|
380
|
+
Pyjo.URL.new('http://example.com/test/Pyjo').path.merge('DOM/HTML')
|
|
381
|
+
|
|
382
|
+
# "http://example.com/DOM/HTML"
|
|
383
|
+
Pyjo.URL.new('http://example.com/test/Pyjo').set(path='/DOM/HTML')
|
|
384
|
+
|
|
385
|
+
# "http://example.com/test/DOM/HTML"
|
|
386
|
+
Pyjo.URL.new('http://example.com/test/Pyjo').set(path='DOM/HTML')
|
|
387
|
+
|
|
388
|
+
# "http://example.com/test/Pyjo/DOM/HTML"
|
|
389
|
+
Pyjo.URL.new('http://example.com/test/Pyjo/').set(path='DOM/HTML')
|
|
390
|
+
"""
|
|
391
|
+
# Old path
|
|
392
|
+
if self._path is None:
|
|
393
|
+
self._path = Pyjo.Path.new()
|
|
394
|
+
return self._path
|
|
395
|
+
|
|
396
|
+
@path.setter
|
|
397
|
+
def path(self, value: str | bytes | Pyjo.Path.object) -> None:
|
|
398
|
+
# New path
|
|
399
|
+
self._path = value if isinstance(value, Pyjo.Path.object) else self.path.merge(value)
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def path_query(self) -> str:
|
|
403
|
+
"""::
|
|
404
|
+
|
|
405
|
+
path_query = url.path_query
|
|
406
|
+
url.path_query = '/foo/bar?a=1&b=2'
|
|
407
|
+
|
|
408
|
+
Normalized version of :attr:`path` and :attr:`query`. ::
|
|
409
|
+
|
|
410
|
+
# "/test?a=1&b=2"
|
|
411
|
+
Pyjo.URL.new('http://example.com/test?a=1&b=2').path_query
|
|
412
|
+
|
|
413
|
+
# "/"
|
|
414
|
+
Pyjo.URL.new('http://example.com/').path_query
|
|
415
|
+
"""
|
|
416
|
+
query = self.query.to_str()
|
|
417
|
+
return self.path.to_str() + (f'?{query}' if query else '')
|
|
418
|
+
|
|
419
|
+
@path_query.setter
|
|
420
|
+
def path_query(self, value: str | None) -> None:
|
|
421
|
+
if value is None:
|
|
422
|
+
return
|
|
423
|
+
m = re.match(r'^([^?#]*)(?:\?([^#]*))?', value)
|
|
424
|
+
assert m is not None
|
|
425
|
+
self.path = m[1]
|
|
426
|
+
if m[2] is not None:
|
|
427
|
+
self.query = m[2]
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def protocol(self) -> str:
|
|
431
|
+
"""::
|
|
432
|
+
|
|
433
|
+
proto = url.protocol
|
|
434
|
+
|
|
435
|
+
Normalized version of :attr:`scheme`. ::
|
|
436
|
+
|
|
437
|
+
# "http"
|
|
438
|
+
Pyjo.URL.new('HtTp://example.com').protocol
|
|
439
|
+
"""
|
|
440
|
+
return (self.scheme or '').lower()
|
|
441
|
+
|
|
442
|
+
@property
|
|
443
|
+
def query(self) -> Pyjo.Parameters.object:
|
|
444
|
+
"""::
|
|
445
|
+
|
|
446
|
+
query = url.query
|
|
447
|
+
url.query = {'merge': 'to'}
|
|
448
|
+
url.query = ['append', 'with']
|
|
449
|
+
url.query = ('replace', 'with')
|
|
450
|
+
url.query = 'a=1&b=2'
|
|
451
|
+
url.query = Pyjo.Parameters.new()
|
|
452
|
+
|
|
453
|
+
Query part of this URL, key/value pairs in a ``list`` will be appended with
|
|
454
|
+
:meth:`Pyjo.Parameters.Pyjo_Parameters.append`, key/value pairs in a
|
|
455
|
+
``dict`` merged with :meth:`Pyjo.Parameters.Pyjo_Parameters.merge`, and
|
|
456
|
+
key/value pairs in a ``tuple`` replace the parameters, defaults to a
|
|
457
|
+
:mod:`Pyjo.Parameters` object. ::
|
|
458
|
+
|
|
459
|
+
# "2"
|
|
460
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').query.param('b')
|
|
461
|
+
|
|
462
|
+
# "b=2&a=2&c=3"
|
|
463
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').query.merge('a', 2, 'c', 3)
|
|
464
|
+
|
|
465
|
+
# "http://example.com?a=2&c=3"
|
|
466
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').set(query=('a', 2, 'c', 3))
|
|
467
|
+
|
|
468
|
+
# "http://example.com?a=2&a=3"
|
|
469
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').set(query=('a', [2, 3]))
|
|
470
|
+
|
|
471
|
+
# "http://example.com?b=2&a=2&c=3"
|
|
472
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').set(query={'a': 2, 'c': 3})
|
|
473
|
+
|
|
474
|
+
# "http://example.com?b=2"
|
|
475
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').set(query={'a': None})
|
|
476
|
+
|
|
477
|
+
# "http://example.com?a=1&b=2&a=2&c=3"
|
|
478
|
+
Pyjo.URL.new('http://example.com?a=1&b=2').set(query=['a', 2, 'c', 3])
|
|
479
|
+
"""
|
|
480
|
+
# Old parameters
|
|
481
|
+
if self._query is None:
|
|
482
|
+
self._query = Pyjo.Parameters.new()
|
|
483
|
+
return self._query
|
|
484
|
+
|
|
485
|
+
@query.setter
|
|
486
|
+
def query(self, value: Anything) -> None:
|
|
487
|
+
q = self.query
|
|
488
|
+
|
|
489
|
+
# Replace with tuple
|
|
490
|
+
if isinstance(value, tuple):
|
|
491
|
+
q.pairs = []
|
|
492
|
+
q.append(*cast(tuple[Anything, ...], value))
|
|
493
|
+
|
|
494
|
+
# Merge with dict
|
|
495
|
+
elif isinstance(value, dict):
|
|
496
|
+
q.merge(*(x for pair in cast(dict[Anything, Anything], value).items() for x in pair))
|
|
497
|
+
|
|
498
|
+
# Append list
|
|
499
|
+
elif isinstance(value, list):
|
|
500
|
+
q.append(*cast(list[Anything], value))
|
|
501
|
+
|
|
502
|
+
# New parameters
|
|
503
|
+
elif isinstance(value, Pyjo.Parameters.object):
|
|
504
|
+
self._query = value
|
|
505
|
+
else:
|
|
506
|
+
q.parse(value)
|
|
507
|
+
|
|
508
|
+
def to_abs(self, base: 'Pyjo_URL | None' = None) -> Self:
|
|
509
|
+
"""::
|
|
510
|
+
|
|
511
|
+
abs = url.to_abs()
|
|
512
|
+
abs = url.to_abs(Pyjo.URL.new('http://example.com/foo'))
|
|
513
|
+
|
|
514
|
+
Return a new :mod:`Pyjo.URL` object cloned from this relative URL and turn it
|
|
515
|
+
into an absolute one using :attr:`base` or provided base URL. ::
|
|
516
|
+
|
|
517
|
+
# "http://example.com/foo/baz.xml?test=123"
|
|
518
|
+
Pyjo.URL.new('baz.xml?test=123').to_abs(Pyjo.URL.new('http://example.com/foo/bar.html'))
|
|
519
|
+
|
|
520
|
+
# "http://example.com/baz.xml?test=123"
|
|
521
|
+
Pyjo.URL.new('/baz.xml?test=123').to_abs(Pyjo.URL.new('http://example.com/foo/bar.html'))
|
|
522
|
+
|
|
523
|
+
# "http://example.com/foo/baz.xml?test=123"
|
|
524
|
+
Pyjo.URL.new('//example.com/foo/baz.xml?test=123').to_abs(Pyjo.URL.new('http://example.com/foo/bar.html'))
|
|
525
|
+
"""
|
|
526
|
+
abs = self.clone()
|
|
527
|
+
if abs.is_abs:
|
|
528
|
+
return abs
|
|
529
|
+
|
|
530
|
+
# Scheme
|
|
531
|
+
base = base if base is not None else abs.base
|
|
532
|
+
abs.base = base
|
|
533
|
+
abs.scheme = base.scheme
|
|
534
|
+
|
|
535
|
+
# Authority
|
|
536
|
+
if abs.host:
|
|
537
|
+
return abs
|
|
538
|
+
abs.userinfo, abs.host, abs.port = base.userinfo, base.host, base.port
|
|
539
|
+
|
|
540
|
+
# Absolute path
|
|
541
|
+
path = abs.path
|
|
542
|
+
if path.leading_slash:
|
|
543
|
+
return abs
|
|
544
|
+
|
|
545
|
+
# Inherit path
|
|
546
|
+
if not path.parts:
|
|
547
|
+
abs.path = base.path.clone().canonicalize()
|
|
548
|
+
|
|
549
|
+
# Query
|
|
550
|
+
if not abs.query.to_str():
|
|
551
|
+
abs.query = base.query.clone()
|
|
552
|
+
|
|
553
|
+
# Merge paths
|
|
554
|
+
else:
|
|
555
|
+
abs.path = base.path.clone().merge(path).canonicalize()
|
|
556
|
+
|
|
557
|
+
return abs
|
|
558
|
+
|
|
559
|
+
def to_str(self) -> str:
|
|
560
|
+
"""::
|
|
561
|
+
|
|
562
|
+
string = url.to_str()
|
|
563
|
+
|
|
564
|
+
Turn URL into a string. Note that :attr:`userinfo` will not be included for
|
|
565
|
+
security reasons. ::
|
|
566
|
+
|
|
567
|
+
# "http://pyjoyment.org"
|
|
568
|
+
Pyjo.URL.new().set(scheme='http', host='pyjoyment.org').to_str()
|
|
569
|
+
|
|
570
|
+
# "http://pyjoyment.org"
|
|
571
|
+
Pyjo.URL.new('http://daniel:s3cret@pyjoyment.org').to_str()
|
|
572
|
+
"""
|
|
573
|
+
return self._string(False)
|
|
574
|
+
|
|
575
|
+
def to_unsafe_str(self) -> str:
|
|
576
|
+
"""::
|
|
577
|
+
|
|
578
|
+
string = url.to_unsafe_str()
|
|
579
|
+
|
|
580
|
+
Same as :meth:`to_str`, but includes :attr:`userinfo`. ::
|
|
581
|
+
|
|
582
|
+
# "http://daniel:s3cret@pyjoyment.org"
|
|
583
|
+
Pyjo.URL.new('http://daniel:s3cret@pyjoyment.org').to_unsafe_str()
|
|
584
|
+
"""
|
|
585
|
+
return self._string(True)
|
|
586
|
+
|
|
587
|
+
# Not in Mojolicious: urllib of the standard library
|
|
588
|
+
def to_urllib(self) -> SplitResult:
|
|
589
|
+
"""::
|
|
590
|
+
|
|
591
|
+
split = url.to_urllib()
|
|
592
|
+
|
|
593
|
+
Turn URL into a :class:`urllib.parse.SplitResult`, for the standard library.
|
|
594
|
+
Unlike :meth:`to_str`, it includes :attr:`userinfo`, as the result holds all
|
|
595
|
+
parts of the URL. ::
|
|
596
|
+
|
|
597
|
+
# "isabel"
|
|
598
|
+
Pyjo.URL.new('http://isabel:s3cret@pyjoyment.org/foo').to_urllib().username
|
|
599
|
+
"""
|
|
600
|
+
return urlsplit(self.to_unsafe_str())
|
|
601
|
+
|
|
602
|
+
@property
|
|
603
|
+
def username(self) -> str | None:
|
|
604
|
+
"""::
|
|
605
|
+
|
|
606
|
+
username = url.username
|
|
607
|
+
|
|
608
|
+
Username part of :attr:`userinfo`. ::
|
|
609
|
+
|
|
610
|
+
# "isabel"
|
|
611
|
+
Pyjo.URL.new('http://isabel:s3cret@pyjoyment.org').username
|
|
612
|
+
"""
|
|
613
|
+
m = re.match(r'^([^:]+)', self.userinfo or '')
|
|
614
|
+
return m[1] if m else None
|
|
615
|
+
|
|
616
|
+
def _string(self, unsafe: bool) -> str:
|
|
617
|
+
# Scheme
|
|
618
|
+
url = ''
|
|
619
|
+
if proto := self.protocol:
|
|
620
|
+
url += f'{proto}:'
|
|
621
|
+
|
|
622
|
+
# Authority
|
|
623
|
+
auth = self.host_port
|
|
624
|
+
if auth is not None:
|
|
625
|
+
auth = _encode(auth, rb"^A-Za-z0-9\-._~!$&'()*+,;=:\[\]")
|
|
626
|
+
if unsafe and (info := self.userinfo) is not None:
|
|
627
|
+
auth = _encode(info, rb"^A-Za-z0-9\-._~!$&'()*+,;=:") + '@' + (auth or '')
|
|
628
|
+
if auth is not None:
|
|
629
|
+
url += f'//{auth}'
|
|
630
|
+
|
|
631
|
+
# Path and query
|
|
632
|
+
path = self.path_query
|
|
633
|
+
url += path if not auth or not path or re.match(r'[/?]', path) else f'/{path}'
|
|
634
|
+
|
|
635
|
+
# Fragment
|
|
636
|
+
if (fragment := self.fragment) is None:
|
|
637
|
+
return url
|
|
638
|
+
return url + '#' + _encode(fragment, rb"^A-Za-z0-9\-._~!$&'()*+,;=:@/?")
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _decode(string: str) -> str:
|
|
642
|
+
return decoded if (decoded := decode('UTF-8', string)) is not None else string
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def _encode(string: str, pattern: bytes) -> str:
|
|
646
|
+
return url_escape(string.encode('UTF-8'), pattern).decode('ascii')
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
new = Pyjo_URL.new
|
|
650
|
+
object = Pyjo_URL
|
|
651
|
+
URL = Pyjo_URL
|