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/Upload.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.Upload - Upload
|
|
3
|
+
====================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Upload import Upload
|
|
7
|
+
|
|
8
|
+
upload = Upload()
|
|
9
|
+
print(upload.filename)
|
|
10
|
+
upload.move_to('/home/sri/foo.txt')
|
|
11
|
+
|
|
12
|
+
# Or import the module, as in Google Python Style Guide
|
|
13
|
+
|
|
14
|
+
import Pyjo.Upload
|
|
15
|
+
|
|
16
|
+
upload = Pyjo.Upload.new()
|
|
17
|
+
print(upload.filename)
|
|
18
|
+
upload.move_to('/home/sri/foo.txt')
|
|
19
|
+
|
|
20
|
+
:mod:`Pyjo.Upload` is a container for uploaded files.
|
|
21
|
+
|
|
22
|
+
See also
|
|
23
|
+
--------
|
|
24
|
+
|
|
25
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
26
|
+
|
|
27
|
+
Ported from `Mojo::Upload <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Upload.pm>`_
|
|
28
|
+
of Mojolicious 9.49.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import Pyjo.Asset
|
|
32
|
+
import Pyjo.Base
|
|
33
|
+
import Pyjo.Headers
|
|
34
|
+
|
|
35
|
+
import builtins
|
|
36
|
+
|
|
37
|
+
from typing import Self
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
type Anything = builtins.object
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Pyjo_Upload(Pyjo.Base.object):
|
|
44
|
+
"""
|
|
45
|
+
:mod:`Pyjo.Upload` inherits all methods from :mod:`Pyjo.Base` and
|
|
46
|
+
implements the following new ones.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
asset: Pyjo.Asset.object | None = None,
|
|
53
|
+
filename: str | None = None,
|
|
54
|
+
headers: Pyjo.Headers.object | None = None,
|
|
55
|
+
name: str | None = None,
|
|
56
|
+
) -> None:
|
|
57
|
+
"""::
|
|
58
|
+
|
|
59
|
+
upload = Pyjo.Upload.new()
|
|
60
|
+
upload = Pyjo.Upload.new(name='foo', filename='foo.txt', asset=Pyjo.Asset.Memory.new())
|
|
61
|
+
|
|
62
|
+
Construct a new :mod:`Pyjo.Upload` object.
|
|
63
|
+
"""
|
|
64
|
+
self.asset = asset
|
|
65
|
+
"""::
|
|
66
|
+
|
|
67
|
+
asset = upload.asset
|
|
68
|
+
upload.asset = Pyjo.Asset.File.new()
|
|
69
|
+
|
|
70
|
+
Asset containing the uploaded data, usually a :mod:`Pyjo.Asset.File`
|
|
71
|
+
or :mod:`Pyjo.Asset.Memory` object.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
self.filename = filename
|
|
75
|
+
"""::
|
|
76
|
+
|
|
77
|
+
filename = upload.filename
|
|
78
|
+
upload.filename = 'foo.txt'
|
|
79
|
+
|
|
80
|
+
Name of the uploaded file.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
self.headers = headers
|
|
84
|
+
"""::
|
|
85
|
+
|
|
86
|
+
headers = upload.headers
|
|
87
|
+
upload.headers = Pyjo.Headers.new()
|
|
88
|
+
|
|
89
|
+
Headers for upload, usually a :mod:`Pyjo.Headers` object.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
self.name = name
|
|
93
|
+
"""::
|
|
94
|
+
|
|
95
|
+
name = upload.name
|
|
96
|
+
upload.name = 'foo'
|
|
97
|
+
|
|
98
|
+
Name of the upload.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def move_to(self, to: Anything) -> Self:
|
|
102
|
+
"""::
|
|
103
|
+
|
|
104
|
+
upload = upload.move_to('/home/sri/foo.txt')
|
|
105
|
+
|
|
106
|
+
Move uploaded data into a specific file.
|
|
107
|
+
"""
|
|
108
|
+
self._asset().move_to(to)
|
|
109
|
+
return self
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def size(self) -> int:
|
|
113
|
+
"""::
|
|
114
|
+
|
|
115
|
+
size = upload.size
|
|
116
|
+
|
|
117
|
+
Size of uploaded data in bytes.
|
|
118
|
+
"""
|
|
119
|
+
return self._asset().size
|
|
120
|
+
|
|
121
|
+
def slurp(self) -> bytes:
|
|
122
|
+
"""::
|
|
123
|
+
|
|
124
|
+
content = upload.slurp()
|
|
125
|
+
|
|
126
|
+
Read all uploaded data at once.
|
|
127
|
+
"""
|
|
128
|
+
return self._asset().slurp()
|
|
129
|
+
|
|
130
|
+
# The asset, which an upload without one does not have, as in Mojo
|
|
131
|
+
def _asset(self) -> Pyjo.Asset.object:
|
|
132
|
+
if (asset := self.asset) is None:
|
|
133
|
+
raise AttributeError("'NoneType' object has no attribute 'slurp'")
|
|
134
|
+
return asset
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
new = Pyjo_Upload.new
|
|
138
|
+
object = Pyjo_Upload
|
|
139
|
+
Upload = Pyjo_Upload
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.UserAgent.CookieJar - Cookie jar for HTTP user agents
|
|
3
|
+
==========================================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
import Pyjo.Cookie.Response
|
|
7
|
+
|
|
8
|
+
from Pyjo.UserAgent.CookieJar import CookieJar
|
|
9
|
+
|
|
10
|
+
# Add response cookies
|
|
11
|
+
jar = CookieJar()
|
|
12
|
+
jar.add(
|
|
13
|
+
Pyjo.Cookie.Response.new(
|
|
14
|
+
name='foo',
|
|
15
|
+
value='bar',
|
|
16
|
+
domain='localhost',
|
|
17
|
+
path='/test',
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Or import the module, as in Google Python Style Guide
|
|
22
|
+
|
|
23
|
+
import Pyjo.Cookie.Response
|
|
24
|
+
import Pyjo.URL
|
|
25
|
+
import Pyjo.UserAgent.CookieJar
|
|
26
|
+
|
|
27
|
+
# Add response cookies
|
|
28
|
+
jar = Pyjo.UserAgent.CookieJar.new()
|
|
29
|
+
jar.add(
|
|
30
|
+
Pyjo.Cookie.Response.new(
|
|
31
|
+
name='foo',
|
|
32
|
+
value='bar',
|
|
33
|
+
domain='localhost',
|
|
34
|
+
path='/test',
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Find request cookies
|
|
39
|
+
for cookie in jar.find(Pyjo.URL.new('http://localhost/test')):
|
|
40
|
+
print(cookie.name)
|
|
41
|
+
print(cookie.value)
|
|
42
|
+
|
|
43
|
+
:mod:`Pyjo.UserAgent.CookieJar` is a minimalistic and relaxed cookie jar used
|
|
44
|
+
by :mod:`Pyjo.UserAgent`, based on :rfc:`6265`.
|
|
45
|
+
|
|
46
|
+
See also
|
|
47
|
+
--------
|
|
48
|
+
|
|
49
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
50
|
+
|
|
51
|
+
Ported from `Mojo::UserAgent::CookieJar <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/UserAgent/CookieJar.pm>`_
|
|
52
|
+
of Mojolicious 9.49.
|
|
53
|
+
|
|
54
|
+
Classes
|
|
55
|
+
-------
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import Pyjo.Base
|
|
59
|
+
import Pyjo.Cookie.Request
|
|
60
|
+
import Pyjo.Cookie.Response
|
|
61
|
+
import Pyjo.File
|
|
62
|
+
import Pyjo.Path
|
|
63
|
+
import Pyjo.Transaction
|
|
64
|
+
import Pyjo.URL
|
|
65
|
+
|
|
66
|
+
from Pyjo.String.Unicode import split
|
|
67
|
+
|
|
68
|
+
import builtins
|
|
69
|
+
import os
|
|
70
|
+
import re
|
|
71
|
+
import time
|
|
72
|
+
|
|
73
|
+
from collections.abc import Callable
|
|
74
|
+
from typing import Any, Self
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
type Anything = builtins.object
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Unlike Mojolicious: the file says that Pyjoyment generated it
|
|
81
|
+
COMMENT = '# Netscape HTTP Cookie File\n# This file was generated by Pyjoyment! Edit at your own risk.\n\n'
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Pyjo_UserAgent_CookieJar(Pyjo.Base.object):
|
|
85
|
+
"""
|
|
86
|
+
:mod:`Pyjo.UserAgent.CookieJar` inherits all methods from :mod:`Pyjo.Base`
|
|
87
|
+
and implements the following new ones.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
*,
|
|
93
|
+
file: str | os.PathLike[str] | None = None,
|
|
94
|
+
ignore: Callable[[Pyjo.Cookie.Response.object], Any] | None = None,
|
|
95
|
+
max_cookie_size: int = 4096,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""::
|
|
98
|
+
|
|
99
|
+
jar = Pyjo.UserAgent.CookieJar.new()
|
|
100
|
+
jar = Pyjo.UserAgent.CookieJar.new(file='cookies.txt')
|
|
101
|
+
|
|
102
|
+
Construct a new :mod:`Pyjo.UserAgent.CookieJar` object.
|
|
103
|
+
"""
|
|
104
|
+
self.file = file
|
|
105
|
+
"""::
|
|
106
|
+
|
|
107
|
+
file = jar.file
|
|
108
|
+
jar.file = '/home/sri/cookies.txt'
|
|
109
|
+
|
|
110
|
+
File to :meth:`load` cookies from and :meth:`save` cookies to in
|
|
111
|
+
Netscape format. ::
|
|
112
|
+
|
|
113
|
+
# Save cookies to file
|
|
114
|
+
jar.file = 'cookies.txt'
|
|
115
|
+
jar.save()
|
|
116
|
+
|
|
117
|
+
# Empty cookie jar and load cookies from file
|
|
118
|
+
jar.file = 'cookies.txt'
|
|
119
|
+
jar.empty().load()
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
self.ignore = ignore
|
|
123
|
+
"""::
|
|
124
|
+
|
|
125
|
+
ignore = jar.ignore
|
|
126
|
+
jar.ignore = lambda cookie: ...
|
|
127
|
+
|
|
128
|
+
A callback used to decide if a cookie should be ignored by
|
|
129
|
+
:meth:`collect`. ::
|
|
130
|
+
|
|
131
|
+
# Ignore all cookies
|
|
132
|
+
jar.ignore = lambda cookie: True
|
|
133
|
+
|
|
134
|
+
# Ignore cookies for domains "com", "net" and "org"
|
|
135
|
+
jar.ignore = lambda cookie: cookie.domain in ('com', 'net', 'org')
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
self.max_cookie_size = max_cookie_size
|
|
139
|
+
"""::
|
|
140
|
+
|
|
141
|
+
size = jar.max_cookie_size
|
|
142
|
+
jar.max_cookie_size = 4096
|
|
143
|
+
|
|
144
|
+
Maximum cookie size in bytes, defaults to ``4096`` (4KiB).
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
self._jar: dict[str, list[Pyjo.Cookie.Response.object]] = {}
|
|
148
|
+
|
|
149
|
+
def add(self, *cookies: Pyjo.Cookie.Response.object) -> Self:
|
|
150
|
+
"""::
|
|
151
|
+
|
|
152
|
+
jar = jar.add(*cookies)
|
|
153
|
+
|
|
154
|
+
Add multiple :mod:`Pyjo.Cookie.Response` objects to the jar.
|
|
155
|
+
"""
|
|
156
|
+
size = self.max_cookie_size
|
|
157
|
+
for cookie in cookies:
|
|
158
|
+
# Convert max age to expires
|
|
159
|
+
if (age := _number(cookie.max_age)) is not None:
|
|
160
|
+
cookie.expires = 0 if age <= 0 else age + int(time.time())
|
|
161
|
+
|
|
162
|
+
# Check cookie size
|
|
163
|
+
if len(cookie.value or '') > size:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# Replace cookie
|
|
167
|
+
if not (domain := (cookie.domain or '').lower()):
|
|
168
|
+
continue
|
|
169
|
+
if not (path := cookie.path):
|
|
170
|
+
continue
|
|
171
|
+
if not len(name := cookie.name or ''):
|
|
172
|
+
continue
|
|
173
|
+
jar = self._jar.setdefault(domain, [])
|
|
174
|
+
jar[:] = [*(c for c in jar if _compare(c, path, name, domain)), cookie]
|
|
175
|
+
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def all(self) -> list[Pyjo.Cookie.Response.object]:
|
|
179
|
+
"""::
|
|
180
|
+
|
|
181
|
+
cookies = jar.all()
|
|
182
|
+
|
|
183
|
+
Return all :mod:`Pyjo.Cookie.Response` objects that are currently stored
|
|
184
|
+
in the jar. ::
|
|
185
|
+
|
|
186
|
+
# Names of all cookies
|
|
187
|
+
for cookie in jar.all():
|
|
188
|
+
print(cookie.name)
|
|
189
|
+
"""
|
|
190
|
+
jar = self._jar
|
|
191
|
+
return [cookie for domain in sorted(jar) for cookie in jar[domain]]
|
|
192
|
+
|
|
193
|
+
def collect(self, tx: Pyjo.Transaction.object) -> None:
|
|
194
|
+
"""::
|
|
195
|
+
|
|
196
|
+
jar.collect(Pyjo.Transaction.HTTP.new())
|
|
197
|
+
|
|
198
|
+
Collect response cookies from transaction.
|
|
199
|
+
"""
|
|
200
|
+
url = tx.req.url
|
|
201
|
+
for cookie in tx.res.cookies():
|
|
202
|
+
# Validate domain
|
|
203
|
+
host = (url.ihost or '').lower()
|
|
204
|
+
if not cookie.domain:
|
|
205
|
+
cookie.domain, cookie.host_only = host, True
|
|
206
|
+
domain = cookie.domain.lower()
|
|
207
|
+
if (ignore := self.ignore) and ignore(cookie):
|
|
208
|
+
continue
|
|
209
|
+
if host != domain and (not host.endswith(f'.{domain}') or re.search(r'\.\d+$', host)):
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
# Validate path
|
|
213
|
+
path = Pyjo.Path.new(cookie.path if cookie.path is not None else url.path.to_dir().to_abs_str())
|
|
214
|
+
path.trailing_slash = False
|
|
215
|
+
if not _path(path := path.to_abs_str(), url.path.to_abs_str()):
|
|
216
|
+
continue
|
|
217
|
+
cookie.path = path
|
|
218
|
+
self.add(cookie)
|
|
219
|
+
|
|
220
|
+
def empty(self) -> Self:
|
|
221
|
+
"""::
|
|
222
|
+
|
|
223
|
+
jar = jar.empty()
|
|
224
|
+
|
|
225
|
+
Empty the jar.
|
|
226
|
+
"""
|
|
227
|
+
self._jar = {}
|
|
228
|
+
return self
|
|
229
|
+
|
|
230
|
+
def find(self, url: Pyjo.URL.object) -> list[Pyjo.Cookie.Request.object]:
|
|
231
|
+
"""::
|
|
232
|
+
|
|
233
|
+
cookies = jar.find(Pyjo.URL.new())
|
|
234
|
+
|
|
235
|
+
Find :mod:`Pyjo.Cookie.Request` objects in the jar for
|
|
236
|
+
:mod:`Pyjo.URL` object. ::
|
|
237
|
+
|
|
238
|
+
# Names of all cookies found
|
|
239
|
+
for cookie in jar.find(Pyjo.URL.new('http://example.com/foo')):
|
|
240
|
+
print(cookie.name)
|
|
241
|
+
"""
|
|
242
|
+
found: list[Pyjo.Cookie.Request.object] = []
|
|
243
|
+
domain = host = (url.ihost or '').lower()
|
|
244
|
+
path = url.path.to_abs_str()
|
|
245
|
+
while domain:
|
|
246
|
+
if (old := self._jar.get(domain)) is not None:
|
|
247
|
+
# Grab cookies
|
|
248
|
+
new = self._jar[domain] = []
|
|
249
|
+
for cookie in old:
|
|
250
|
+
if cookie.host_only and host != cookie.domain:
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
# Check if cookie has expired
|
|
254
|
+
if (expires := cookie.expires) is not None and int(time.time()) > expires:
|
|
255
|
+
continue
|
|
256
|
+
new.append(cookie)
|
|
257
|
+
|
|
258
|
+
# Taste cookie
|
|
259
|
+
if cookie.secure and url.protocol != 'https':
|
|
260
|
+
continue
|
|
261
|
+
if not _path(cookie.path or '', path):
|
|
262
|
+
continue
|
|
263
|
+
found.append(Pyjo.Cookie.Request.new(name=cookie.name, value=cookie.value))
|
|
264
|
+
|
|
265
|
+
# Remove another part
|
|
266
|
+
domain = re.sub(r'^[^.]*\.*', '', domain, count=1)
|
|
267
|
+
|
|
268
|
+
return found
|
|
269
|
+
|
|
270
|
+
def load(self) -> Self:
|
|
271
|
+
"""::
|
|
272
|
+
|
|
273
|
+
jar = jar.load()
|
|
274
|
+
|
|
275
|
+
Load cookies from :attr:`file`.
|
|
276
|
+
"""
|
|
277
|
+
if not (file := self.file) or not os.access(file, os.R_OK):
|
|
278
|
+
return self
|
|
279
|
+
|
|
280
|
+
# Unlike Mojolicious: the file is UTF-8, where Mojo reads and writes characters as they are, and warns about
|
|
281
|
+
# wide characters
|
|
282
|
+
content = str(Pyjo.File.path(file).slurp('UTF-8'))
|
|
283
|
+
|
|
284
|
+
# Unlike Mojolicious: a line may end with CRLF, as curl and http.cookiejar write the file on Windows, where Mojo
|
|
285
|
+
# keeps the carriage return in the value of the cookie
|
|
286
|
+
for line in split(r'\r?\n', content):
|
|
287
|
+
# Prefix used by curl for HttpOnly cookies
|
|
288
|
+
line = line or ''
|
|
289
|
+
httponly = line.startswith('#HttpOnly_')
|
|
290
|
+
line = line.removeprefix('#HttpOnly_')
|
|
291
|
+
if line.startswith('#'):
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
# Unlike Mojolicious: the empty value of a cookie at the end of the line is a field, where Mojo's split
|
|
295
|
+
# leaves it out and the cookie with it
|
|
296
|
+
values = [value or '' for value in split('\t', line, -1)]
|
|
297
|
+
if len(values) != 7:
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
self.add(
|
|
301
|
+
Pyjo.Cookie.Response.new(
|
|
302
|
+
domain=values[0].removeprefix('.'),
|
|
303
|
+
host_only=values[1] == 'FALSE',
|
|
304
|
+
path=values[2],
|
|
305
|
+
secure=values[3] != 'FALSE',
|
|
306
|
+
expires=None if values[4] == '0' else _number(values[4]),
|
|
307
|
+
name=values[5],
|
|
308
|
+
value=values[6],
|
|
309
|
+
httponly=httponly,
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return self
|
|
314
|
+
|
|
315
|
+
def prepare(self, tx: Pyjo.Transaction.object) -> None:
|
|
316
|
+
"""::
|
|
317
|
+
|
|
318
|
+
jar.prepare(Pyjo.Transaction.HTTP.new())
|
|
319
|
+
|
|
320
|
+
Prepare request cookies for transaction.
|
|
321
|
+
"""
|
|
322
|
+
if not self._jar:
|
|
323
|
+
return
|
|
324
|
+
req = tx.req
|
|
325
|
+
req.cookies(*self.find(req.url))
|
|
326
|
+
|
|
327
|
+
def save(self) -> Self:
|
|
328
|
+
"""::
|
|
329
|
+
|
|
330
|
+
jar = jar.save()
|
|
331
|
+
|
|
332
|
+
Save cookies to :attr:`file`.
|
|
333
|
+
"""
|
|
334
|
+
if not (file := self.file):
|
|
335
|
+
return self
|
|
336
|
+
|
|
337
|
+
final = Pyjo.File.path(file)
|
|
338
|
+
tmp = Pyjo.File.path(f'{os.fspath(file)}.{os.getpid()}')
|
|
339
|
+
tmp.spew(COMMENT + self.to_str(), 'UTF-8').move_to(final)
|
|
340
|
+
|
|
341
|
+
return self
|
|
342
|
+
|
|
343
|
+
def to_str(self) -> str:
|
|
344
|
+
"""::
|
|
345
|
+
|
|
346
|
+
string = jar.to_str()
|
|
347
|
+
|
|
348
|
+
Stringify cookies in Netscape format.
|
|
349
|
+
"""
|
|
350
|
+
lines: list[str] = []
|
|
351
|
+
for cookie in self.all():
|
|
352
|
+
expires = cookie.expires if cookie.expires is not None else 0
|
|
353
|
+
# Unlike Mojolicious: the domain of a cookie for subdomains starts with a dot, as curl and the
|
|
354
|
+
# MozillaCookieJar of http.cookiejar write it and the latter needs it, where Mojo leaves the dot out
|
|
355
|
+
domain = cookie.domain or ''
|
|
356
|
+
line = [
|
|
357
|
+
domain if cookie.host_only else f'.{domain}',
|
|
358
|
+
'FALSE' if cookie.host_only else 'TRUE',
|
|
359
|
+
cookie.path or '',
|
|
360
|
+
'TRUE' if cookie.secure else 'FALSE',
|
|
361
|
+
str(int(expires) if float(expires).is_integer() else expires),
|
|
362
|
+
cookie.name or '',
|
|
363
|
+
cookie.value or '',
|
|
364
|
+
]
|
|
365
|
+
lines.append('\t'.join(line))
|
|
366
|
+
|
|
367
|
+
return '\n'.join([*lines, ''])
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _compare(cookie: Pyjo.Cookie.Response.object, path: str, name: str, domain: str) -> bool:
|
|
371
|
+
return cookie.path != path or cookie.name != name or cookie.domain != domain
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# Not in Mojolicious: a number as Perl's looks_like_number takes it, as the value of an integer or a float
|
|
375
|
+
def _number(value: Anything) -> int | float | None:
|
|
376
|
+
if value is None or isinstance(value, bool):
|
|
377
|
+
return None
|
|
378
|
+
try:
|
|
379
|
+
number = float(str(value))
|
|
380
|
+
except ValueError:
|
|
381
|
+
return None
|
|
382
|
+
return int(number) if number.is_integer() else number
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _path(path1: str, path2: str) -> bool:
|
|
386
|
+
return path1 == '/' or path1 == path2 or path2.startswith(f'{path1}/')
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
new = Pyjo_UserAgent_CookieJar.new
|
|
390
|
+
object = Pyjo_UserAgent_CookieJar
|
|
391
|
+
CookieJar = Pyjo_UserAgent_CookieJar
|
Pyjo/UserAgent/Proxy.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.UserAgent.Proxy - User agent proxy manager
|
|
3
|
+
===============================================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.UserAgent.Proxy import Proxy
|
|
7
|
+
|
|
8
|
+
proxy = Proxy()
|
|
9
|
+
proxy.detect()
|
|
10
|
+
print(proxy.http)
|
|
11
|
+
|
|
12
|
+
# Or import the module, as in Google Python Style Guide
|
|
13
|
+
|
|
14
|
+
import Pyjo.UserAgent.Proxy
|
|
15
|
+
|
|
16
|
+
proxy = Pyjo.UserAgent.Proxy.new()
|
|
17
|
+
proxy.detect()
|
|
18
|
+
print(proxy.http)
|
|
19
|
+
|
|
20
|
+
:mod:`Pyjo.UserAgent.Proxy` manages proxy servers for :mod:`Pyjo.UserAgent`.
|
|
21
|
+
|
|
22
|
+
See also
|
|
23
|
+
--------
|
|
24
|
+
|
|
25
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
26
|
+
|
|
27
|
+
Ported from `Mojo::UserAgent::Proxy <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/UserAgent/Proxy.pm>`_
|
|
28
|
+
of Mojolicious 9.49.
|
|
29
|
+
|
|
30
|
+
Classes
|
|
31
|
+
-------
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
import Pyjo.Base
|
|
35
|
+
import Pyjo.Transaction
|
|
36
|
+
import Pyjo.URL
|
|
37
|
+
|
|
38
|
+
from Pyjo.String.Unicode import split
|
|
39
|
+
|
|
40
|
+
import os
|
|
41
|
+
|
|
42
|
+
from typing import Self
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Pyjo_UserAgent_Proxy(Pyjo.Base.object):
|
|
46
|
+
"""
|
|
47
|
+
:mod:`Pyjo.UserAgent.Proxy` inherits all methods from :mod:`Pyjo.Base` and
|
|
48
|
+
implements the following new ones.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, *, http: str | None = None, https: str | None = None, no: list[str] | None = None) -> None:
|
|
52
|
+
"""::
|
|
53
|
+
|
|
54
|
+
proxy = Pyjo.UserAgent.Proxy.new()
|
|
55
|
+
proxy = Pyjo.UserAgent.Proxy.new(http='http://127.0.0.1:8080')
|
|
56
|
+
|
|
57
|
+
Construct a new :mod:`Pyjo.UserAgent.Proxy` object.
|
|
58
|
+
"""
|
|
59
|
+
self.http = http
|
|
60
|
+
"""::
|
|
61
|
+
|
|
62
|
+
http = proxy.http
|
|
63
|
+
proxy.http = 'socks://sri:secret@127.0.0.1:8080'
|
|
64
|
+
|
|
65
|
+
Proxy server to use for HTTP and WebSocket requests.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
self.https = https
|
|
69
|
+
"""::
|
|
70
|
+
|
|
71
|
+
https = proxy.https
|
|
72
|
+
proxy.https = 'http://sri:secret@127.0.0.1:8080'
|
|
73
|
+
|
|
74
|
+
Proxy server to use for HTTPS and WebSocket requests.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Renamed from Mojolicious: not, a keyword of Python, as the environment variable NO_PROXY names it
|
|
78
|
+
self.no = no
|
|
79
|
+
"""::
|
|
80
|
+
|
|
81
|
+
no = proxy.no
|
|
82
|
+
proxy.no = ['localhost', 'intranet.mojolicious.org']
|
|
83
|
+
|
|
84
|
+
Domains that don't require a proxy server to be used.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def detect(self) -> Self:
|
|
88
|
+
"""::
|
|
89
|
+
|
|
90
|
+
proxy = proxy.detect()
|
|
91
|
+
|
|
92
|
+
Check environment variables ``HTTP_PROXY``, ``http_proxy``,
|
|
93
|
+
``HTTPS_PROXY``, ``https_proxy``, ``NO_PROXY`` and ``no_proxy`` for proxy
|
|
94
|
+
information. Automatic proxy detection can be enabled with the
|
|
95
|
+
``PYJO_PROXY`` environment variable.
|
|
96
|
+
"""
|
|
97
|
+
env = os.environ
|
|
98
|
+
self.http = env.get('HTTP_PROXY') or env.get('http_proxy')
|
|
99
|
+
self.https = env.get('HTTPS_PROXY') or env.get('https_proxy')
|
|
100
|
+
self.no = [domain or '' for domain in split(',', env.get('NO_PROXY') or env.get('no_proxy') or '')]
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def is_needed(self, domain: str) -> bool:
|
|
104
|
+
"""::
|
|
105
|
+
|
|
106
|
+
boolean = proxy.is_needed('intranet.example.com')
|
|
107
|
+
|
|
108
|
+
Check if request for domain would use a proxy server.
|
|
109
|
+
"""
|
|
110
|
+
return not any(domain.endswith(no) for no in self.no or [])
|
|
111
|
+
|
|
112
|
+
def prepare(self, tx: Pyjo.Transaction.object) -> None:
|
|
113
|
+
"""::
|
|
114
|
+
|
|
115
|
+
proxy.prepare(Pyjo.Transaction.HTTP.new())
|
|
116
|
+
|
|
117
|
+
Prepare proxy server information for transaction.
|
|
118
|
+
"""
|
|
119
|
+
if os.environ.get('PYJO_PROXY') not in (None, '', '0'):
|
|
120
|
+
self.detect()
|
|
121
|
+
req = tx.req
|
|
122
|
+
url = req.url
|
|
123
|
+
if not self.is_needed(url.host or ''):
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# HTTP proxy
|
|
127
|
+
proto = url.protocol
|
|
128
|
+
if (http := self.http) and proto == 'http':
|
|
129
|
+
req.proxy = Pyjo.URL.new(http)
|
|
130
|
+
|
|
131
|
+
# HTTPS proxy
|
|
132
|
+
if (https := self.https) and proto == 'https':
|
|
133
|
+
req.proxy = Pyjo.URL.new(https)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
new = Pyjo_UserAgent_Proxy.new
|
|
137
|
+
object = Pyjo_UserAgent_Proxy
|
|
138
|
+
Proxy = Pyjo_UserAgent_Proxy
|